Anatomy of ASP.NET Core Requests - Steve Gordon - NDC Oslo 2020

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
as i was saying what i want to kind of cover is it's looking a little bit lower than we would normally look at asp.net core so typically most people will be familiar with working with maybe the mvc pattern and using controllers and actions in your applications um and a lot of that just works almost by magic you don't have to do a huge amount of wiring up or code there's a lot of conventions that mean asp.net core functions as you'd expect what i want to do is dive into what's actually happening under there so that we can start to think about places where we might be able to replace some of the default behavior with our own behavior typically for achieving some kind of cross-cutting concern that you have in the application so we're going to begin and we're going to begin right from the very start of this journey where a client wants to make a http request to us so let's imagine that we've got an api running on stevejordan.k forward slash api forward slash books and the client wants to communicate with that api and get some data from it so the very first stage that we'll just touch on very briefly and i apologize the the animations in this slide i spent hours on are going to be really janky over about five frames per second that we get on webex but let's do our best to kind of follow the pattern so the first phase is the client has this address for a server and it needs to understand where that server actually is what server serves that traffic so we're not going to go diving deep into dns but fundamentally there's a you know a set of lookups and referrals that may happen through various levels of the domain name system until we come to a domain name server that can provide us back an ip address that we can communicate with so now we know the ip address of the server that hosts the website next we need to connect to that server and the client that might be a browser client being controlled by an actual user or it may be a programmatic client maybe it's c sharp using http current in the code base to communicate with an api it needs to connect to that server so a port is going to be selected on the client machine for the outbound connection and then on the web server we'll either be talking over http which will typically use port 80 or https over 443 so in this case let's imagine we're doing uh the sort of https because this is going to be going over a public internet so the first thing that happens is we need to connect the client to the server and we're going to use tcp so while we're using http as our application layer protocol underneath that we have the communication protocol the transport protocol of tcp and so there's a brief client server handshake that takes place client basically says hello are you there the servers are here and then we establish our connection now there's another layer that we then need to have on top of this because of the secured communications that we want so we layer in the tls slab and this requires another set of handshaking to happen between client and server client basically is going to get a certificate from the server to verify that they are who they say they are we'll do a decision on how we're actually going to encrypt traffic and what key exchange we're going to use for that and then eventually we'll have our tls connection the next stage is that the client needs to send the actual http request over the wire so here is a very basic http request it's basically just ascii test so in the first line here we have the get method being defined so this is the http method that we're going to be using for the request the resource that we want slash api slash books and then the protocol version so 1.1 of http then we can have some headers these are basically key value pairs so here we have the host header defined steve jordan.coding okay so this will allow the routing system further down uh and maybe even the web server to determine okay which actual maybe virtual website am i hosting on my uh on my server that can handle this request so this will help with that routing and we have a number of fairly common headers so accept header is like we prefer json in this exchange we prefer the english language we can accept encodings that allow supportion uh supporting of compression for the request as well and the response and we also identify who we are with a user agent here so this is us being a good citizen and saying yeah this is kind of who we are this is the version of us that's talking to you so that the downstream service knows who we are and then we conclude the request with a blank line all of this so far we don't have to worry about doing if you're using something like http client to send the request it's doing all of this work for you uh with a sort of an abstraction over what the request message looks like next we actually send the request over the network to the server that we've connected to and at the other end of that connection in asp.net core we have kestrel kestrel has been there since asp.net core 1.0 it was at the time a brand new web server written from the ground up by the asp.net team and designed to be very efficient and support running the web server in process with the web application so fundamentally asp.net core applications are just a console application it's a console application that runs a host that host is the process in the application that basically keeps that process alive listening for things like down signals from the operating system and then within the host that can fire up a web server and kestrel conforms to the iserver interface that's defined by that set of hosting layer and can actually act in as a server it binds itself to whatever ports it's been told to and listens for requests coming in so what this looks like at quite a high level is that the client is going to make its request potentially over the internet potentially over an internal network to the kestrel web server on uh for on the connection that it's established uh kestrel then handles all of these requests and these connections through a series of connection middleware now we're not going to go too deep into connection middleware and how that all works inside kestrel it's quite a deep topic but just be aware that if you have particular networking requirements or connection requirements you can actually write your own connection middleware and register into the kestrel's kind of request handling pipeline pretty like much like you would middleware inside ash connect core what we'll be talking about shortly guess we're going to pass that request then into asp.net core itself asp.net core is going to do some work hopefully producing a response and then that response flows back out through the connection that it's established with the client if we zoom in a little bit we'll have a look at what happens in a little bit more detail so the request has come in it's reached kestrel kestrel's first job is to parse the http request so it's getting bytes over the network and it needs to interpret those bytes as a valid http message so this involves various steps of parsing the request line making sure that it includes the method the resource the protocol version parsing out any headers that may be on the request possibly reading any content in if it's a request with content and ultimately its job is to translate that into a http context so this is the object representation that basically covers all of the information that we need through the rest of the system about the ongoing request so this is essentially the contract between kestrel and asp.net core so this carries with it things like details of what headers were found on the request message what resource are they looking for so what path or query string maybe did they include in the url and later on other bits of information get tagged onto this context as it flows through the system there's a features collection this is something that you can use in your middleware and in your applications if you want to apply a feature that basically gets carried around a state with that context and flows through the rest of the request handling pipeline so this context flows into asp.net core and your application code you basically create the response within there and that gets attached to the http context which gets passed back to kestrel and then sent over the wire if we zoom in even further we can see what happens after we've got that http context so the next stage inside asp.net core itself is that we're actually going to flow that request and it's hdb context into asp.net core middleware so asp.net core middleware is a series of components basically that have access to the request on the way in and the response on the way out so why is this useful well middleware basically is a great place for any kind of cross-cutting behaviors cross-cutting concerns for requests and responses you register your middleware pipeline and that gets read at startup and used for the lifetime of that application and so this means you control exactly what happens to each request as it flows through your system each piece of middleware may choose to simply pass the request on to the next middleware in the pipeline it might enrich the request message so maybe even change values or add on items to the http context or features to the features collection and then it will pass it on with that change or it may choose to short circuit the pipeline torque circuiting is basically where the middleware decides actually i can return some kind of valid response so it will set the response message on the context and then not pass it on to the next middleware in the chain instead it will return back to kestrel so each piece of middleware runs in turn and eventually once that middleware pipeline is run it's going to hand off to some endpoint or usually mvc for the rest of the request handling once that's built a response it flows back through the middleware and this is important it flows through the middleware again but in reverse order from what you originally registered it and so each piece of middleware now has the opportunity to inspect the response that's been added onto that contact and maybe make decisions about it maybe do some kind of metrics and monitoring uh based on what response is going out of your server about any kind of cross-cutting concern that you have and then again that flows out over to the client so this is how we define a middleware pipeline in a standard application so this is the configure method of the startup class so you will hopefully have seen this if you've used asp.net core before this is the typical example out of one of the templates and the configure method has access to the eye application builder this is the way that we define all of the pipeline for processing our requests so we add middleware to that pipeline with these various app.use something methods so if we take a look at this first one for example this one adds in the developer exception page middleware and this middleware basically takes a request that's had an exception phone somewhere in the system and writes out the stack trace so that's really useful during development to understand what went wrong and where it blew up but that's not something we want to leak out into the public internet we wouldn't want to give people a sort of detailed understanding of how our application works internally it might be an attack vector so this piece of middleware is registered conditionally by checking the environment we're running in so this app starts up the configure method gets called and at that point asp.net core has established what environment it's running in based on environment variables and config settings the other thing you can use here to potentially opt in or out of middleware at sort of startup is putting uh conditions around your middleware registrations that use the eye configuration as well so configuration features that you might have set in your app settings.json could control the startup order for your application the next piece of middleware use https redirection is a piece of middleware which sort of produces an example of where we might short circuit the request so this middleware sees the request coming in and if the scheme of that request is http and not https then this piece of middleware is not going to pass the request on for the rest of the pipeline instead what it's going to do is send back a redirect to the client and say actually no you need to be using this url that uses https so this is how https is enforced in asp.net core applications so that's a middleware that sort of makes that selective decision based on interpreting some of the data that's on the request in the http context we'll come back to some of these other ones in a while but if we draw that sort of uh sort of set of middleware out as a diagram this is kind of what it looks like so we have the pipeline being invoked on the left and then middleware gets him called in the order it was registered top to bottom of the configure method now there's one interesting thing on this diagram that some of you may be spotting and that's two pieces of middleware show up here that aren't in the configure method and these get registered in a special way by a different part of asp.net core's startup system there's a feature in there called i startup filter and this is sorry a startup filter and this is an interface that you can implement in your applications and register with the di container and when asp.net core starts up it will run each of those startup filters and the goal of the startup filter is to register middleware into the pipeline right at the beginning of that pipeline so it doesn't give you as much control over the placement and ordering of the middleware but certain kinds of middleware do lend themselves to this so the two examples that microsoft place in there by default is host filtering which is going to look at the host header and see if it matches the allowed hosts for that application so in your appsettings.json you can define what hosts your web servers should actually respond for by default it puts in a star so it allows any host but if you do start adding in hosts hostnames that you support those will get filtered out via that piece of middleware very early in the pipeline the next one is about dealing with forwarded headers from things like reverse proxies so typically we'll put our asp.net core applications behind some kind of load balancer maybe even a reverse proxy like nginx or we may run it behind at iis um and if you do those are typically going to add sort of exported four type headers onto the request and what we can do in this middleware is translate those into the standard headers that has connect calls are looking for so once the endpoint that gets the request handles it and send a response that response then flows back through that middleware pipeline this time in reverse order we can write our own middleware and the middleware is basically in line in this example so we can use the app.used method here to register this inline middleware we get access to a context which is the http context and the next variable here relates to the request delegate which is the next piece of middleware in the pipeline so inside this method we can write logic that controls the behavior of this application when it's handling requests this example is fairly basic so what we're going to do is start a stopwatch as soon as the request comes to us and then immediately pass it on to the next middleware in the pipeline that will flow through all of the middleware somewhere the response will be generated the response will generally flow back through the middleware until it reaches us at which point we stop our stopwatch and in this case just record a metric this is a very sort of low-tech way of adding sort of uh response timing metrics into an application um i do something a bit like this in a real application just slightly more sophisticated the only problem with this is we do need a service from di and although we can get to that by going on to the context accessing request services which is the iservice provider and then calling get required service we're kind of using the service locator pattern here which isn't super clean so for anything but really basic middleware i tend to recommend following the pattern of creating a middleware class this is a class this is basically your middleware component and the other thing you can do with these is add them into libraries you get packages and share them around your business if you have common features that you want to apply to lots of applications this doesn't require a particular interface it's all done by a sort of convention and duct typing so this method um constructor here must accept a request delegate and that is the representation of the next piece of middleware in the pipeline anything else that we want to take from di we can also have injected into here then we have the invoke async method and we also must provide this and it must accept the http context representing the current request the code inside here is basically what we just looked at in line except now we're using the dependency injected iometric recorder and our code has all now been nicely wrapped up in this single class to call this from our application code then we can now use the use middleware extension method on the eye application builder to register our middleware so in this example we're registering this really early in the pipeline so we can time the end to end request if you want to get a little bit cleaner with that kind of stuff and you're building maybe libraries you're sharing around a good sort of best practice there is to build these extension methods on the our application builder so inside your extension method you just call use middleware but now this means that as a caller when they're adding this into the pipeline it's just a little bit cleaner a little bit clearer what's being added and all of those middleware that we're seeing added by default use this convention of providing an extension method for registering the middleware the next thing i want to talk about is endpoint routing and this is quite a deep topic um it could probably sort of take up a whole session in its own uh but what i'm going to do is try and give you the highlights so in asp.net core 2.2 the team introduced this endpoint routing feature before asp.net core 2.2 what would happen is the request would flow through middleware and the final piece of middleware would be mvc middleware usually and the mvc middleware would receive the request and then use the sort of mvc part of the framework to actually process the request and inside there mvc would do all of the logic for figuring out where that request should root are you which controller and which action now that worked just worked just fine but the problem is it meant that mvc is the only component in the entire sort of application pipeline that can actually know where a request is going to be rooted i what controller and what action and this means that if we want to do something like authorization for example we can't do that until we're inside mvc because we haven't determined yet which action is going to get invoked and if if we know what once we know what that action is we can determine does it require authorization and if so what level of authorization so what the team did in 2.2 which wasn't on by default um but has since been switched on and made the default pattern in three zero and 3.1 is introduce endpoint routing so with endpoint routing all of the routing logic has been lifted up outside of mvc so it stands alone as additional middleware in the application and what this means is we can do further things based on that routing with inside the middleware pipeline directly so the easiest way to come try and understand this is to take a look at the digger method again so the first piece of middleware for routing is newsrooting and this piece of middleware when the application is running it's responsible for interpreting what the request is looking for so is it a get request and what's the path it's going to be and matching that to one of the known routes for the application but no routes get figured out at startup but they can also be updated at runtime as well so use routing is about figuring out what endpoint is going to be called the further down middleware here use endpoints is about actually invoking that endpoint so now we know where we're going to send this request send it there the bit that is slightly confusing is that within use endpoints you have this delegate to set up the endpoints so this doesn't actually happen as the app's running this kind of happens earlier and in this case we're using the convenience method map controllers which basically means that startup the application is going to locate all of the controllers in the assembly find all of the action methods that relate to those controllers and then figure out how they should map to routing uh so you know are they get methods are they uh what route attributes do they have and that will build up this routing table so these two pieces work together what this means is that between these two pieces of middleware we're kind of in what they call the routing zone this is where we have middleware that now knows where this request is going to end up and this means that these two pieces of middleware are what we call endpoint aware so this means that they both can understand what endpoint this request is going to end up at and also have a bunch of metadata about that endpoint that allow them to determine if they need to take action so use authorization for example didn't exist as middleware before 3.0 because it had to happen inside mvc now it's middleware because it can look at the request it can tell which endpoint that's going to map to and it will be able to check if there's an authorized attribute for example on the action method and if there is but the user hasn't logged in then we can return the challenge response to them because we know that they're not allowed to see that without logging in the easiest way to get a bit of a feel for how this works and excuse me sort of leaning in as i uh as i demo this is to actually look uh as a little sample app um all of the slides and all of the code i'm going to show you is online i'll make sure i point out the link at the end and i'll share it in the chat as well so you can get a look at all of this so inside my startup class in my little sample app here i've got two pools to use middleware so i've got one here before use routing and one after use routing it's the same piece of middleware but we're going to run it once before we use routing and once after if we take a look at the code for this um it's a fairly simple piece of middleware and when invoke async gets called what it's going to do is it's going to first call get endpoint on the http context and so if endpoint routing has uh sorry use routing has already been called and it's matched this to an endpoint this will contain details about what about what that endpoint is if it hasn't matched then this would just be null so in this piece of middleware i'm just uh conditionally dumping data out to the logs so i'm saying if it's a type root endpoint which is the type that action methods typically are maps to then write out the display name the root pattern that matched it and any metadata and if it's null we'll just write that out now if i run this code in postman i'm just going to make a request to one of my endpoints here and this is coming through that middleware this is coming through the first time so this is before we pulled um use routine so here the endpoint is null because user routing hasn't yet run to match this to a particular endpoint so at this point we can't really do anything based on that root information but if i continue it will flow through this the first middleware it'll flow through user routing and it will come back in to our second registration of this same middleware and this time we do have an endpoint so this endpoint has some metadata we know that this is going to go to the books controller and the get method uh we have a bunch of metadata which is just really a collection of any kind of object that we know about this endpoint so in this case we can see all of the attributes that apply to the controller and the actions within it and so this allows us to make decisions by inspecting this uh if we can fly out certain metadata we might be able to decide on what to do so for example if we had the authorize attribute on an action method that would show up here and that's exactly what use authorization is using to make determinations about whether or not someone's allowed to continue their request or not i'll just continue that request and greatly we've got a response there so after we've run all of the middleware pipeline and the application moved through um we're going to then enter into mvc so mvc is the programming paradigm that hopefully many of you are familiar with it's the model view controller approach it is this is used for web api development still where we have these models and controllers you can use it for ui applications if you're applying views as well and razer pages itself sits on top of this model and it's kind of a layer over the top so inside mvc we have controllers and this is a basic example so a controller can derive from controller base which provides it some default behaviors that we might need we have the api controller attribute which is going to add some features on for apis using some of the techniques we're about to see it has the root attribute so this is how the routing system uh when it's mapping the controller roots can determine what the root should be in this case the square brackets and controller means use the name so slash books without the controller suffix and inside here we can inject things via dependency injection if we want to and we can then create action methods any public method of a controller is considered an action method by default so in this case the action method is a get it doesn't have any parameters it has the http get attribute which ensures that uh only get requests will map to this endpoint and it returns some kind of i action result in this case we're using the action result of t which basically says we're going to return a result with some data and this is the type of that data and in this case it's using uh the ok method here which comes from the controller base that's going to translate to an ok object result and that object result has an object in this case it's the the list of books that we've retrieved from some repository system don't worry too much about action results at the moment we'll be getting to them in a moment so next uh we'll just take a quick dive into mvc at a 10 000 foot view before we go a little bit deeper so once we enter mvc some code is going to run and eventually uh the controller factory is going to need to create an instance of the controller so this is where we initialize and instance the controller her request once we have the controller some more code will run and eventually an action invoker will kick in and that that job there is to actually call your action method so whatever method has mapped to the request that's being handled after that we have that by action result and the i action result doesn't represent data at this point it represents a strongly typed model of what data and what type of http response we may be sending only gets converted into a actual http response when we execute the i action result and that happens in the result execution phase finally that data then gets returned so in the case of an api normally we're just going to return serialized json data if you're doing uh something with views then the view rendering phase takes place here so the view result which takes the view and the model and kind of combines them together and then executes them to produce html will run we're going to be focusing more on the data result option here so what happens after uh we enter mvc is we basically run through what's called the filter pipeline it is a pipeline that handles how mvc sort of responds to requests internally and you can kind of think of this really a very big state machine nvc is basically a state machine that runs through different states as the request flows through uh bumping it through the various states until it flows back out again so where this starts from um is we have the middleware on the left there which represents the you know the middleware pipeline that we've already seen and the request is going to flow from the left so the first thing that happens inside mvc is high authorization filters will get executed so eye authorization filters are a place where we can run authorization code in the on authorization method now because of endpoint routing today these pretty much will probably do nothing because now use authorization is its own middleware and has probably already run then the authorization filters will be able to detect that and won't run again only if uh use authorization hasn't been called earlier would authorization kick in inside the mvc feature set itself you can write your own authorization filter you implement eye authorization filter but it's really not recommended to do so the reason for that is there's a whole authorization system inside asp.net core that's built to be very pluggable and very controllable so the recommended pattern if you need to customize authorization logic is to use authorization policies and authorization handlers maybe even resource base off but all of those are kind of higher level features and the eye authorization filter that's registered by default will kick in and execute those policies for us so don't normally want to be modifying these authorization filters might short-circuit the pipeline so if someone isn't allowed to um run then they will be getting the forbidden result in this case because we've said actually the thing you're trying to access you're not allowed to see the next filter in the pipeline is resource filters so these kick in immediately after authorization will have run and they have an on resource executing method and at this point again we can do some additional cross-cutting concerns that wrap the entire mvc request response pipeline excuse me the only problem with this is that again this is a feature that's kind of been taken away now we have the concept of endpoint aware middleware it's better to write middleware that inspects the endpoints and makes conditional decisions about whether it runs or not previously the reason you'd use an eye resource filter is because you'd want to know and you would know at this stage which controller and which action is going to get the request so resource filters do allow us some functionality though and we'll take a look at one shortly again a resource filter can short-circuit the pipeline if it wants to returning some kind of result and then if not it will pass the request into the rest of mvc nvc is going to do all of its work and it's going to return the response and on the way out on resource executed gets called and we have another final opportunity to potentially change or augment behavior based on the result that's going out so let's have a look at another quick demo here so what i'm going to do is look at an example of a very basic resource tool so in this example uh what i'm doing if i just make this fit onto the large font size um so this is an iron resource filter um it's got an on resource executing method that we're using and what we're saying in here is read the response see if there's a request header called preview and if there is what's it that is value so the idea is that we're going to have a section of our api that isn't behind authorization but that does require someone to opt into accessing it because it's not yet complete so we're saying if they haven't got the preview header or if they've got the header and it's false then we're going to short circuit the pipeline we do that by setting the result on the context so here we're saying just pretend that you couldn't find the resource turn the not found result otherwise we'll just continue through and we'll just add a response header and by default because we haven't set the context the mvc state machine will just pass this on to either the next filter or to the next part of the processing pipeline the way i've applied this resource filter is i've got this author's controller in my application and in this case i've added my my filter to the controller so you can add filters either to action methods or to entire controllers so all action methods of the controller or globally for all actions or controllers so here we said apply this to anything under authors that we want that feature so this is the method we're going to call here so hopefully we'll make our request and i haven't set any preview headers at the moment so i make my request and we get a 404 not found response what's interesting is we never even hit this action method so because we had a filter in place that short-circuited this request pipeline it never even made it as far as an action method if i add on a preview header here that has the value of true and resend this request through you can see now it does hit the action method inside here because it passed through the filter and if i continue that um you can see that we get the the fake data from this endpoint so this is one of the ways that we can filter that behavior now today we could write that with endpoint aware middleware because we could also look for the presence of an attribute on the metadata after the use routing has been called and we could have middleware that then says if this attribute is present apply this same behavior we'll come back to slides and we'll continue the journey shortly uh so the next filter is middle air filters and these are really just a special kind of resource filter and you might wonder why we have a filter for middleware when we can run middleware in the pipeline and this again stems from the pre endpoint routine days where we might want to apply some middleware but only selectively to some endpoints and the only way to do that before endpoint routing was to do it inside the mvc pipeline where we knew the end point where we knew the controller and action that we were going to invoke so again middleware filters may short-circuit the pipeline here's a very basic example we're not going to use one uh in the demos because it's pretty much depreciated now but basically what you do is create a class that has a configure method so very much like the signature of the startup class and in there you have the eye application builder and you call various methods to add in the middleware that you want um in this case we're adding the response compression middleware only and then to conditionally apply that to a controller you use the middleware filter attribute passing in the type of the middleware pipeline that you want to use and so this means in this example anything that goes through the office controller will also run that additional middleware to potentially apply response compression so all of these first three filters are places we can customize logic but they're all places where the behavior of doing so has pretty much been replaced by using endpoint routing and the endpoint aware middleware so if you find yourself writing these do have a think and see oh can we do this if we do get endpoint in our middleware and then we look at the attributes that are on those endpoints can we make the same sort of decisions about whether we're filtering the requests and doing something special with them or not so the next phase after this initial step is the controller creations this is where the factory for creating controllers is going to run it's going to create an instance of the controller for that particular request and then the next phase begins which is all around invoking the action on that on that controller that we're going to be sending the request through so the first piece of that and one of the most important is model binding we have to map any data that we need off of a request and match it to parameters of the action method because the action method we've looked at so far had no parameters but it's very common for example you might have an endpoint that allows you to get data about one particular book and that might take an int id as one of its parameters and so somehow mvc needs to work out what id you want to pass into that action method when it gets invoked and that's where model binding comes in so mvc has a suite of model binders by default and many of these will handle sort of 99 of your scenarios the model binders are basically responsible for saying okay given this type that i'm trying to create so maybe the int id do i have a model binder that can provide integers and map them off of the request so there's a simple type model binder that's responsible for these kind of basic operations and potentially making sure that the data coming in on that request could be set to the value so it's easier to see that in a little bit of an example so here we've got a search method which has two parameters we have a string keyword and an int page size so somehow when this action method is invoked we need that data we need those parameters so mvc has a series of what are known as value providers and these are the the things that basically inspect parts of the request like the query string like the headers and see if there's values on there that could potentially fulfill the requirements of the action method so here for example we require a keyword on the string so the value providers are going to be asked do you have a value that could potentially satisfy this well value providers are essentially a key value sort of lookup so they say well yeah i've got something called keyword here's its value i'll pump that into this action method when we invoke it integers very similar so here we have a page size that matches on the query string as well and as long as the value of that is a valid integer that could also be used to satisfy this request where things get more complex is when you've got um sort of strongly typed input models for things like post requests so if we have an endpoint that allows us to post a book into the system we'll probably accept maybe a book input model and on there we'll have a number of properties like id title isbn number for example so we need to get those bound from the data on the request and most likely that data exists in the actual request body so this is where input formatters come in input formatters can look at the content type of the request coming in and determining can i take that content and can i turn it into an object so in the case of mvc by default there's a json input formatter that's registered so if the content type is application json the model binder will use the input formatter for json to try and deserialize the content of the request body and turn it into whatever object we're trying to bind and so we can use the default input formatter for json we can add a default formatter that is included in the box but not enabled for xml or you can write your own if you've got uh requirements use protobuf or message pack or your own proprietary binary serialization format you can write a formatter to interpret that data off of the request let's have a quick look at model binding we won't spend too long on this because again it's a feature that you can easily overuse um but i have one example that i have used in practice that i think is fairly reasonable so here i've got a method which allows us to search for a set of books by the the date range that they were published within so my method accepts this date range type if we take a look at date range we'll see it's a fairly simple struct it's got a start date and an end date the important thing is these are read only so these are getters but no setters so the only way to create this object is through the constructor and then from then on it's considered immutable and in here we do some logic around um the date as well so the end date can't be before the start date that wouldn't be valid for our day range concept now one option if we wanted to avoid custom model binding here is that we could just have a simpler type that does have a getter and a set of two properties basically an input model and we could map to that and then we could try and create our mutable date range as our domain model inside our controller and generally for more complex types that's pretty reasonable but for something as simple as this where we might use this across a number of action methods in our in our controllers this makes makes quite a lot of sense so if we take a look at what the binder looks like quickly here we basically have the code for binding and i won't talk about every step here i'll try and summarize it and you can take a look at the comments in the code yourself afterwards basically what we're saying is when you're trying to bind we expect by convention in our system the start date and then data have these names on on some of the data however it's provided that could be in query string it could be on the root parameters for example so then what we say is we ask them the value provider system do you have any values with these keys these names and it will return to us a result which can be none one or many values that match in this case if if we've got none we haven't got the values we want what we're going to do is we're going to add a model error to the model state so the model state is a representation of how the binding has gone basically and mvc can use this for future uh validation steps in handling the response so the model state here we add an error saying okay we didn't find the start of date or end date something failed we set the result as failed and we simply return otherwise if we do have those values we set the model state to say yep we found the values these are the values we're going to use we then access those values which are by default strings so then we then pass those strings to dates because a date should conform to a sort of well-known date structure and if we parse them and they're both been able to be parsed correctly now we can create our date range object here and so this date range object uh takes the start date and end date then we set the result on the binding context as successful and we set the object that we successfully bound if this fails so if there's an argument exception because the end date's invalid for example then we'll again set model state as failed and we'll add a model error there so that's pretty much the binder there's two ways we can add binding support to an application so the first is in the configure services method when we add nvc or we add controllers in this example we can add to the model binder provider list a model binder provider specifically for our requirements we insert this one in position zero so it runs first and this model binder provider is a fairly simple class that implements our model binder provider and it then has this get binder method and on here we get access to the model type that's trying to be bound and so our comparison is if it's a date range yes we can supply a date range binder for that if not we'll return null which means we haven't found a suitable binder and then the other providers will get asked can you can you match this then this works if you don't own the type in this example we own the date range struct so what we can actually do is add an attribute directly to it that controls the model binder that's used as well so if you own the type this attribute-based approach is quite straightforward if you don't own the type and it's from some external library and you can't modify it then maybe you would use the model binder provider approach so let's see how this works if i go to another endpoint that we're going to call so by range we provide the start date and an end date and we're going to make the request you can see yep we get we get a result if um i change the date on this so that the start date is earlier than the end date which we consider invalid in our system and send again uh you'll see now we get a 400 valve request from the system and it gives us the detail of what's failed in our model so because we've added this custom model binder in we've added this additional logic i'll undo that so i don't forget in a moment after model binding has run uh the next phase is action filters so these implement the i action filter interface these are basically like the filters that have come before them the main difference is at this point we know the bound parameters of the the request so we have actual actual access to those bound arguments basically and we can look at those values and make decisions about whether we let requests continue so this is another place where you might apply some kind of validation logic we'll look at that shortly again they may choose to short-circuit the request path um and just immediately return otherwise what they're going to do is execute the action method so this is where mvc and the framework is just actually going to call finally your action method code anything that you've written inside that action method and so in some applications this may be the first place in the entire pipeline after all of this stuff is run where it's actually running user code code you've provided everything else could all be the default middleware and the default motherbikers so the action method gets invoked and after it gets invoked we get the on action executed called on the filter so again it's another sort of double uh double filter we're on the way in and on the way out so again we'll have a quick look at a a short demo for this one so let's take this binding scenario a little bit further so on my books controller where i accept this we accept a date range and we've validated it's about a date range through binding it but maybe in this action method and maybe in more than just this action method we have a requirement that we only allow the date range to be two years because maybe looking up anything more than two years is too much data for some reason and so if we had to apply that once we could do it inside this action method we could compare the start and end date and if it's greater than two years return some kind of bad request result but if we want to do that in multiple action methods that's going to be code that we'd have to repeat so let's let's be dry and not repeat ourselves so what we can do is we can create a filter which i've applied in this case to this particular action method two year date range filter and in here the logic is fairly straightforward as well so on action executing we're going to see if the action arguments contains a value called date range and if so is it a type date range and if it is is that date range greater than two years if it's greater than two years then instead of actually passing this through we're going to short circuit by setting the results again and we're going to set a bad request object result here so this is a bad request with some payload and in this case it's a problem details response that includes the error message so now if i uh make a request to my endpoint again but this time uh well there we go we've already got 2012 on there in 2019 so that's greater than two years if i make my request now instead of hitting the action method it's been filtered out um it's probably even easier to see that if i put a breakpoint on here so this is the action method if we do that one more time it's immediately been returned the response by short circuiting inside the the filter mechanism so this is cross-cutting concerns in the case of our application that we've been able to apply as a filter we're nearly there after action filters um and after action execution there is the possibility that our action method threw an exception so we had some kind of exception in our code or code that we called down into that we haven't appropriately handled so obviously we don't want to blow up the entire web server just because one request has caused an exception so exception filters are about dealing with those exception cases and setting an appropriate response there is a default behavior inside mvc that will handle this for us but if you want to you can write your own iexception filter and in its on exception method control what happens so back to the demo and let's take a look at that example so here is my exception filter you can see it inherits from i exception filter it has a non-exception method and in here what i'm choosing to do is record a metric to a metric system so this is a really good use case for this which is if an exception occurs in the application that's unhandled wouldn't it be nice if our alerting and monitoring system knew about that and could maybe alert us in slack or something well this is exactly what we do in our system so we have an exception occurring we record a metric and then downstream our notifications come out the other thing we're doing here is we've got our own custom format in this api or what an error message looks like so this is the model for that error has a message and it has a flag is error so inside our code we actually can construct and return an instance of that as a serialized json object and then we just set this exception handle to true so that any other exception filters know actually we can register this is we can add it to controllers or actions but i've registered this one in my startup class into this filters collection so this is where we add filters globally that apply to all controllers and all actions which is really useful for something like exception handling so with that exception in the framework if i come in and make a call to an endpoint uh called bang we'll run that we've got an unhandled exception here now this could be deep down into our code but the only way to prevent this with our own code would be to wrap this in a try catch and that may not be very pretty if you have to have a try catch in every action method so by adding our filter handling we don't need to try catch we catch the exception in the exception filter instead so if we let this continue um and why that's run twice uh the request comes through and you can see here our nicely formatted exception message that we decided to return as a signature of this api so we're coming towards the end uh but we're still a little bit more in the in the pipeline so after all of this is run the final set of filters that are going to run are result filters so the main difference with what these have access to that we didn't have access to earlier is we now have the action result with the iaction result that was returned by our action method assuming there was no exception so the i action result is that sort of representation of the data that our response is going to include but it hasn't yet been written to the response content so at this point we can in this filter make some further decisions based on the type of action result or even the data within the action result if we have a cross-cutting concern we'll look at one in a second but action filter sorry result filters run and then the action result itself gets executed and this is where um in an api for example the action result is going to be turned into the appropriate response probably with a json payload so the way that works is if your request includes an object sorry your result includes an object that that object is going to run through an output formatter by default that will be the json output formatter and that will serialize the object to json and then add that onto the response stream you can again add your own output formatters so if you want to send a different type of data you can do that by adding that formatter into the framework uh for again protobuf message pack or some custom proprietary format that you have and at this stage now on the hp context we have the http response uh with the data added to it so headers will have been added and the actual content will be written out to it and then the request sorry the response now flows through back through that middleware um filter pipeline all the way out to the middle there at the end there so let's have a look at one of those result filters quickly and just get an idea of where we could use those so here i have a result filter this is the last modified result filter and what i'm doing here is i'm saying if the result type is okay object result and if the value of that so the object inside the ok object result is of type mutable output model base which is a really terribly named type i added this is a base type that i use for my output models in this example so this includes a last modified date so we assume anything that can be modified in the database will be mapped to a output model that derived from this and here's the rest of the book data what this means is because we have this kind of type that we can look for we can now say well okay if it is that time we'll set the last modified response header to the last modified uh value from the actual object so we're having we're adding a header on here in this example this is why we do this on result executing not executed because we need to write the headers before the output formatter writes the response body because we can't have headers after the response body so this is going to run and if we make a request to this endpoint and send that request through so we've got our response it includes the last modified date and on the headers here we've now added our last modified header um with the value that we we've bought out of the database and this this filter is again referenced globally so this applies to every controller and every action where it's an object result being returned of that type and so this is again another place to apply this cross-cutting concerns and could even be used with certain kinds of caching for example if you have specific requirements to do so so this is the kind of final picture that was about as deep as we're gonna go um there have been some generalizations that i've had to make to fit this into into the hour but the key points are those first three filters authorization resource and middleware are pretty much redundant today with endpoint routing uh which you should be using if you're using 3.0 or 3.1 um basically give us the same functionality but lifted up outside of mvc and so you can do authorization with authorization middleware you can base your sort of resource filter logic inside middleware by making it endpoint aware so calling getendpoint on the context and then interpreting any attributes of that endpoint any metadata about it and similarly with middleware filters you can conditionally apply your middleware controller creation is going to happen and the first then phase is model binding so this is where again we map properties and data from the request onto the types and the um arguments of our action method we then run the action execution phase so this is where the action invoker is going to run and call our actual code so if you haven't done any previous customization this is the first chance where your code is going to run we then get the action result the i action result from that action method and before we execute it we run it through a filter again so we can make a final last minute decision to do something unusual and then if not we execute the action result which may or may not use an output formatter to produce the the serialized data that's going to go on to the response and then all of this flows back out of mvc um as a response that's added onto that http context we flow back into the middleware so a reminder we come out of whatever endpoint whatever action method inside mvc is called and we flow through each of the middleware and the response is set now so those middleware components can potentially log that information or potentially do something related to that data potentially decide to even replace the response entirely and then the hdd context flows to kestrel and kestrel is going to take that data from the http context turn it into a http response message as bytes that it sends down to the client just to kind of close the picture is the response message from our earlier request so the server said great i've been able to do this request for you so it's a 200 okay response it's included some headers so some of these headers such as the server header have been added by kestrel itself some of these are going to be added via sort of mdc's such as the content type that we ended up creating and adding to the response and we've even got our last modified header that we added using our result filter inside asp.net core the response content then is the serialized json representation of an array of books in this example um and so this was produced by that output formatter which saw the the object that we attached to our action result it saw the object it knew how to serialize that into valid json and then it attached it to the http context and onto the response so lots and lots of stuff there um the main thing is don't expect you need to use all of these things in all of your applications most of the time the defaults are going to be working pretty well for you but if there is code that you find yourself repeating particularly across numerous action methods do you think about whether that could be better applied as some kind of filter at the appropriate place in the pipeline and then just use attributes to apply that behavior where you need it in summary uh the requests are handled in asp.net call by kestrel so that's responsible for parsing bytes off of the wire into a http context and passing it into the rest of the asp.net core and later uh producing the bytes that go out back out to the client we have the middleware pipeline gets executed in the order that they're registered so from top to bottom of the configure method and the response flows back through in the reverse order and finally we have this concept of endpoints which is new in sort of 3.0 3.1 uh endpoints are really just an action that's going to invoke and handle our request and that may run through the filter pipeline first which may or may not decide to short-circuit that request handling eventually the action method gets executed which returns an i-action result and then the action result is going to get executed to return the final contact so lots and lots of stuff there um terrible animations coming across over five frames a second um the link that i mentioned earlier so these slides and all of the sample code which is up on my github is at that link there bit.ly forward slash asp.net anatomy um that should take you to that and i've included some other resources there so some links to the microsoft documentation that cover this stuff pretty well as well um you can find me online and perhaps steve jay gordon on twitter so if you have any questions that i haven't kind of covered off i'll do my best to answer them there um we've got like a minute left here so i don't think we'll probably do too many questions on the chat but what i'll do is as soon as we've uh kind of concluded i'll jump into the slack channel for this room and we can kind of keep the discussion going there or over dms if you prefer if you if you prefer to do that so hopefully that was useful hopefully um that's the information you were looking for um lots and lots of stuff there to take away and apply to what you're building thank you very much for coming along and hopefully next year i will see you in person at ndc osler thank you
Info
Channel: NDC Conferences
Views: 10,972
Rating: 5 out of 5
Keywords:
Id: 0UZf_7c_EeE
Channel Id: undefined
Length: 57min 36sec (3456 seconds)
Published: Thu Aug 06 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.