Core3 MVC Request Life Cycle - egnomerator/misc GitHub Wiki
course author: Alex Wolf
related course "The MVC Request Life Cycle":
- these 2 courses cover the application and request life cycles including the MVC-specific stages
- there is significant conceptual overlap, but there are also some major differences to be aware of between the ASP.NET and ASP.NET Core implementations
- don't see any mention of application life cycle events
- a new concept of middleware exists
- whereas in ASP.NET there was an initial life cycle step of "Routing" in which a UrlRoutingModule played a key role in reaching into the MVC framework to select a matching route and retrieve the appropriate HttpHandler, in ASP.NET Core this seems to be handled by middleware
Some initial notes on .NET Core Middleware
- building blocks of the MVC app's HTTP pipeline
-
Endpoint Routing Middleware
- select endpoint to handle the request
- execute the selected endpoint
- in the context of MVC, a controller.action method could be considered an endpoint
Topics
- What is Middleware?
- Creating Middleware
- Program and Startup classes
- building a simple Middleware pipeline
- writing reusable Middleware
- visualizing the MVC Middleware pipeline
- MVC Middleware pipeline internals
The series of components that form the application request pipeline.
- handle incoming requests and generate response
- routing, session, CORS, authentication, caching, all via Middleware
- any number of Middleware can comprise the pipeline
- order matters
- Middleware executes in textual order placement in the source code
- it then executes again in reverse order as the response gets sent back
- can short circuit the pipeline for a given a request and send a response immediately rather than letting the remaining Middleware execute for that request
Configuration options
-
Run()
: generate response (short circuit) -
Use()
: work (incoming), pass on, work (response) -
Map()
: reroute to a branch of Middleware execution
Can implement Middleware inline, but typically middleware is implemented as a class.
Program
- the entry point into the application
- consumes a Startup class
-
creates a web host
- this point (creating a web host) was for some reason hardly mentioned in this course (perhaps considered out of scope, although I would say it's very much in scope)
- this is a key difference from ASP.NET which relies on IIS (or another hosting application--not common) for web hosting
- ASP.NET MVC apps relies on an external hosting process (IIS)
- IIS is a robust time-tried hosting application that has been integral to ASP.NET web app development for many years
- ASP.NET Core implements "self-hosting"
- the application itself listens on a port for requests rather than having an external process (like IIS) listen
- although Kestrel is recommended to sit behind a reverse proxy (e.g. IIS, Nginx, Apache)
- there is an InProcess option available since 2.2 where if the app is configured for this, the result is that the ASP.NET Core app runs in IIS in process rather than out of process
- the hosting functionality is coded in the ASP.NET Core app
- the application itself listens on a port for requests rather than having an external process (like IIS) listen
- these notes barely scratch the surface of this hosting topic
- this point (creating a web host) was for some reason hardly mentioned in this course (perhaps considered out of scope, although I would say it's very much in scope)
Startup
- configures services that will be used by the app via DI
- registers all the Middleware
Three helpful questions to consider when determining if a new piece of Middleware is warranted
- would this be Reusable functionality?
- it would make sense to create a piece of Middleware that can be reused among multiple apps
- this is not a requirement though--Middleware can be justifiable without being useful to multiple apps
- would this address an Application level concern?
- would this fit into the request-driven nature of the Middleware pipeline?
- to create a Middleware class, there is no base Middleware class or interface to inherit/implement
- instead Middleware uses a convention-based structure
- strangely, the convention was not clearly explained in the course
- this is the convention:
- Microsoft Docs
- the Middleware class must include:
- a public constructor with a parameter of type
RequestDelegate
- a public method named
Invoke
orInvokeAsync
; this method must:- return a
Task
- Accept a first parameter of type
HttpContext
- return a
- a public constructor with a parameter of type
appsettings.json file
- this file was mentioned as well as configuring the web host builder to make this file available to the application
- it was used in the demo to make certain settings available
Routing is (within the context of this course covering .NET Core 3) the process of mapping an incoming request to an endpoint
- an endpoint can be thought of as a controller action method, and is to be more clearly defined later
Types of routing
- Conventional Routing
- app-wide patterns to match URL to a controller action method
- in .NET Core, the routing is implemented via Middleware
- evaluated in textual order in the source code
- so define patterns from most specific to most general
- Attribute Routing
- applying patterns in attributes directly to match a controller action method (or a whole controller)
- route value doesn't have to match action method name
- evaluated in parallel
- applying route attributes to controllers and/or action methods, precludes them from being routed to by conventional routing
The Endpoint Routing system is new as of .NET Core 2.2
- it is still implemented as Middleware
What was wrong with the earlier .NET Core routing?
- none of the other Middleware components know which action method will handle the request before it's processed by MVC
- this creates inefficiencies for things like Authorization or CORS Middleware that could benefit from knowing where the request is going ahead of time
- tight coupling of MVC with heavy routing responsibilities
- MVC should just be concerned with generating the response to the request
Endpoint Routing fixes both problems by extracting the routing responsibility out of the MVC Middleware.
- Two Middleware Components in the new Endpoint Routing System
- Endpoint Routing Middleware
- Decides which Endpoint registered with the application should handle the request
- Endpoint Middleware
- Executes the selected Endpoint to generate a response
- Endpoint Routing Middleware
Endpoints (in the context of .NET Core Endpoint Routing) are classes that contain a request delegate and other metadata used to generate a response to an incoming request.
- in the context of MVC that RequestDelegate is a wrapper around a method that instantiates a controller and executes the selected action method
- endpoints also contain metadata used to determine whether their request delegate should be selected for the current request and other purposes
There are a few different types of Endpoints.
Here is a generalized example of a RouteEndpoint:
public class RouteEndpoint : Endpoint
{
public string DisplayName {get;set;}
public string Pattern {get;set;}
public EndpointMetadata Metadata {get;}
public RequestDelegate RequestDelegate {get;}
}
- the metadata could help with route selection or other purposes
- the request delegate executes to generate a response to the request
- in the context of MVC, this delegate wraps controller instantiation and action method execution
- this delegate is the "bridge" between Middleware and MVC
During application startup, a class called ControllerActionEndpointDataSource
is created as part of the app-level services, this class does the following:
- executes its member method called
CreateEndpoints()
which- retrieves all of the controller action methods
- it then creates an Endpoint instance for each one (the wrapper class discussed above)
- it also stores all of the route templates registered with the app
- foreach Endpoint instance, it will either
- associate the Endpoint with the route attribute
- OR if no route attribute, associate the matching conventional route template
Then when requests come in, this data source class is an available resource ...
Incoming Request ...
- the Endpoint Routing Middleware consumes the data source class and uses it to match the incoming request to an Endpoint
- the Endpoint Routing Middleware attaches the matched Endpoint to the Request object for later Middleware to have access to
Demo of custom Middleware
- the custom Middleware was placed right after the
UseRouting()
Endpoint Routing Middleware in the Middleware pipeline - the custom Middleware called
httpContext.SetEndpoint(new Endpoint(/*...*/))
- this replaces any currently set Endpoint
- since the custom Middleware executed after the
UseRouting()
Middleware, this replaced the Endpoint set by the Endpoint Routing Middleware for the rest of the pipeline
- the custom Middleware only conditionally called
httpContext.SetEndpoint(/*...*/)
- it only called SetEndpoint() after checking the currently set Endpoint
- since the Endpoint Routing Middleware had already set the Endpoint, this Middleware could use it (something not possible in the .NET Core Middleware pipeline before this new Endpoint Routing System)
note on razor pages:
- just like the endpoint routing middleware can handle controller actions and route attributes, the same is true for razor pages--in fact all of these routing mechanisms can be used in the same app and will all be considered during the endpoint data source compilation and routing middleware.
Invoker classes
- ResourceInvoker (abstract),
ControllerActionInvoker
(inherits from ResourceInvoker) - these classes orchestrate the MVC pipeline at the time of execution
- "classes" really just means a instance of the
ControllerActionInvoker
using the MVC pipeline logic inherited from ResourceInvoker along with its own action workflow logic
- "classes" really just means a instance of the
- use a state enum and switch statements to act on state overall acting as a state machine for the MVC life cycle
Process
- execute authorization filters
- execute resource filters
- Create Controller instance IF auth and resource filters have not short circuited
- Execute the action workflow
- this workflow is not part of the ResourceInvoker class, this is specific to the derived
ControllerActionInvoker
class - Workflow
- Model Binding
- Action Filters
- Action Execution
- Action Filters
- this workflow is not part of the ResourceInvoker class, this is specific to the derived
- execute result
- Result Filters
- Result execution
- Result Filters
How an Endpoint RequestDelegate is executed
- created during app startup by
ControllerActionEndpointDataSource
and then available for all requests - the request delegate
- creates an ActionContext
- asks an invoker factory to create an IActionInvoker (implemented by
ControllerActionInvoker
) given the ActionContext - returns result of
invoker.InvokeAsync();
Entrance into the MVC Pipeline
-
invoker.InvokeAsync()
is the entrance into the MVC pipeline- use of the state enum containing the MVC pipeline state values
- act on the authorization filters, and act on the resource filters
- potentially short circuit the process before creating a controller depending on these filters
- state ActionBegin is where
InvokeInnerFilterAsync()
is called entering the action workflow-specific logic- here is where the
ControllerActionInvoker
instance asks the controller factory for an instance of a controller- the
ControllerActionInvoker
has another state enum where the values are the action workflow states - it switches on this state similar to how the inherited ResourceInvoker logic switches on the MVC state
- the ActionBegin enum state is where the factory creates an instance of the controller
- the
- here is where the
Filters are:
- components that allow us to inject logic at various stages of the MVC request life cycle
Authorization Filters
- first filters to execute in the mvc pipeline
- must inherit from
Attribute
and implementIAuthorizationFilter
- CAN require constructor injection
- IF so, you can't just apply the attribute directly
- instead, you have to apply via a "type filter" provided by .NET Core
- a type filter wraps the creation of another filter supplying any require dependency(s)
- how to apply
- apply type filter attribute passing in the type of filter
- e.g.
[TypeFilter(typeof(MyCustomFilterAttribute))]
note: there is a "service filter" as well related to the type filter functionality
Resource filters
- execute right after auth filters
- implement
IResourceFilter
methods OnResourceExecuting() and OnResourceExecuted() - the methods wrap both ends of the authorized MVC pipeline
- after the auth filters, resource filters are the first opportunity to inject custom logic within the MVC pipeline
- resource filters are also the last opportunity to inject custom logic within the MVC pipeline
- some common uses
- caching
- short-circuiting
- manipulate incoming data, model binding behavior
- inject logic very early or very late in the MVC pipeline
Middleware filters
- enables running standard Middleware inside the MVC pipeline
- why?
- reuse logic
- delay execution of Middleware logic
- execute Middleware with greater access to MVC context information
- this source also explains some reasoning
- how
- create a piece of Middleware following the convention documented above in the "Middleware by Convention" section
- e.g. we'll call our example Middleware class
Test
- e.g. we'll call our example Middleware class
- create a "mini-pipeline" to define the execution of any desired Middleware
- create a class conventionally implementing the "Configure" method
public class TestPipeline { public void Configure (IApplicationBuilder appBuilder) { appBuilder.UseMiddleware<Test>(); } }
- apply the "mini-pipeline"
- method 1 non-global
- apply directly to a controller or action via attribute
- e.g.
[MiddlewareFilter(typeof(TestPipeline))]
- method 2 global
- source (applying globally was not discussed in this Pluralsight course)
- apply in the startup class ConfigureServices method as an option in the AddMvc method
- e.g.
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { // line of code specific to this example: options.Filters.Add( new MiddlewareFilterAttribute(typeof(TestPipeline))); }); }
- method 1 non-global
- create a piece of Middleware following the convention documented above in the "Middleware by Convention" section
action methods are responsible for determine the data to respond to a request with and responsible for setting the result object (type of response) to be rendered
- the ActionResult is responsible for the actual rendering of the result
- the process of mapping incoming request data to action method parameters
- in ASP.NET Core we can also model bind to controller properties and razor page properties
key components
- model binder providers -- determine the model binder to use for the request
- the model binder -- populates the parameters
- value providers -- retrieve data from the request and supply them to the model binder
- key value structure
-
input formatters
- this is a second type of value provider
- provide complex data from the request body in formats like JSON or XML
- this is a second type of value provider
- the model binder can read from both types of value providers
Custom model binding
- the framework stops looking for the right model binder as soon as it finds one that will work
- when providing custom model binders, it might be necessary to put them before the built-in ones
- can add custom model binders in startup class's ConfigureServices AddMvc() options
Why custom model binding?
- not commonly needed, but 2 particular scenarios
- massage incoming data before the action method rather than handle the mal-formed data in every action method
- handle data formats that the framework doesn't know support
- example CSV (demo showed custom model binder for CSV)
- note: in demo, custom model binder provider was inserted at beginning of the model binder provider list
covered in the course "The MVC Request Life Cycle"
covered in part in the course "The MVC Request Life Cycle"
3 main components
- the view engine: locates the view to be rendered
- the view engine result: reports the results of the view engine search
- the view: renders the data and markup
note: an interesting tour of a compiled razor view file (the C# version of a view) - this was a pretty "simple" view and the compiled file was 200 lines