The MVC Request Life Cycle - egnomerator/misc GitHub Wiki

course author: Alex Wolf

related course "ASP.NET Core 3.0: 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

Defining the request life cycle

A life cycle is

  • a series of steps or events used to handle some type of request or change an application state.

MVC has 2 life cycles:

  1. the application life cycle and
    • from the time that the app process begins running on IIS to the time it stops
    • marked by the application start and end events in the startup file of the app
  2. the request life cycle
    • the sequence of events that runs every time an HTTP request is handled by the application

Request life cycle overview

mvc-request-life-cycle

Entry point: Routing

  • Routing
    • URLRoutingModule matches incoming URL to routes defined in application
    • if request is matched to a route defined in the MVC app, the MVCRouteHandler executes and retrieves an instance of the MVC HTTP Handler (MVCHandler)
    • together the MVCRouteHandler and MVCHandler act as a gateway into the MVC framework
  • Controller Initialization
    • MVCHandler initializes and executes a controller
    • the framework handles converting route data into concrete controller to handle the request (via entities including controller factory and activators)
      • this is also the stage where DI is implemented (and other features)
  • Action Execution
    • model binding
    • action filters
    • Action execution: ActionInvoker selects appropriate action method to invoke on the controller
    • action filters
    • action result
  • Result Execution
    • generates the actual response to the original HTTP request
    • MVC separates declaring the result from executing the result
    • if the result is a view type, the view engine is called
      • view engine find and renders the view
    • if not a view type, the action result will execute on its own

Comparing MVC to Web Forms

Differences

  • request mapping
    • web forms: requests to a web forms app generally correspond to an actual file on disk (~/Home/Index.aspx => Index.aspx)
    • mvc: requests to an MVC app map to a controller and action (~/Home/Index => HomeController.Index())
    • a

Similarities

  • HTTPHandler: both handle requests by implementing an HTTPHandler
    • web forms: each .aspx implements the IHTTPHandler and responds to the request more directly
    • mvc: handler uses request info to construct a controller and generate response dynamically

The Point

  • one platform (ASP.NET) => multiple implementations (HTTPHandlers: WebForms, MVC Framework, Custom handler)

Application and Request life cycle events

Discussion points

  • application start and application end events
  • applying initial configurations (setting configurations ahead of time--before app starts)
  • MVC integration with the Request Life Cycle events
  • Leveraging the life cycle events (with custom code)

The life of an application

MVCApplication (declared in Global.asax.cs)

  • inherits from HTTPApplication class
  • HTTPApplication exposes a number of life cycle events

Application Start event

  • an event that fires when the first request is received
  • can be used to run initial configuration code (register routes, bundles, filters, routes, etc.)
  • routes are registered with the route table inside this app start event before any other life cycle events happen

Other application events fire

Application End event

  • an event that fires when the app ends
  • not a great option for handling application errors
  • not used often, but would be used to ensure certain things happen before an app completely shuts down

PreApplicationStart

  • another option for running initial configuration code
  • it is an attribute
    • example use from demo:
      • [assembly: PreApplicationStartMethod(typeof(MvcApplication), "Register")]
        • note: this attribute must be applied at assembly level; so can't apply to a type or method directly
        • the attribute specifies a type and a member method of that type to run before startup
      • this attribute line was added in Global.asax.cs between the using statements and the namespace
        • but this attribute didn't have to be placed here, in another demo he put the LogModule in its own library and put the attribute in the file containing the LogModule class definition
      • then defined a new Register() method in the MvcApplication class--this is the method that the attribute ensures runs before the Application_Start() method which would normally be the first method to run
      • the new Register() method called HttpApplication.RegisterModule(typeof(LogModule));
      • the LogModule class was added for the demo and implements IHttpModule
    • define a type, give it the attribute, and implement required method(s)
  • often used to register http modules

The life of a request

After the app starts and the http modules are initialized

  • the rest of the app events fire
    • these events fire for all requests
  • A couple specific points of interest:
    1. in spite of the existence of a "MapRequestHandler" event the URL Routing module (which matches a request to a handler handler) runs when on the "ResolveRequestCache" event (actually the Post version of this event)
      • older versions of IIS aren't didn't have the "MapRequestHandler" event, so the URL routing module always ran on the cache event, so it still does for backwards compatibility
    2. most of the MVC framework executes between the two "RequestHandlerExecute" events where the MvcHandler runs

mvc-request-life-cycle-app-events

The Scope of the life cycle

The app life cycle events are framework agnostic

  • the allows the use of common HTTP Modules among multiple apps
  • bc each app would benefit from the common module running any time the event that module cares about is raised for any ASP.NET app (regardless of whether or not it is MVC)

Working with Routing, HttpHandlers, and HttpModules

HttpHandlers

HttpHandlers are classes that implement IHttpHandler and generate a response to an HTTP request.

Only one HttpHandler can execute per request

  • you can reassign the current handler during the life of a request but only one will execute

4 life cycle events that concern http handlers (they concern selecting and then executing a handler)

  1. MapRequestHandler - fires when ASP.NET asks for a handler for a request
  2. PostMapRequestHandler - fires after a handler has been selected
  3. RequestHandlerExecute - fires before a handler executes
  4. PostRequestHandlerExecute - fires after a handler executes

So, the response to an http request is generated when the handler executes (between events 3 and 4 above).

Creating an HttpHandler

  • create a class that implements IHttpHandler interface
    • IHttpHandler interface exposes 2 members
      1. IsReusable
        • a flag that states whether the handler can be used across requests
      2. ProcessRequest()
        • main execution method of classes that implement IHttpHandler and is the method that is responsible for generating a response
        • this method does not specify what type of response has to be created
          • could be files, views, html, plain text, json, etc.
  • register the handler through code or config file

Creating a custom HttpHandler is a pretty rare occurrence

  • most commonly, a custom handler might be used to customize how the framework handles certain types of requests
    • e.g. create a handler to customize how requests for certain file extensions are handled
    • e.g. create a handler to customize how requests for certain MIME types are handled
  • creating a new framework - example SignalR framework has its own handler

Important Concept: Requests intercepted by a custom handler will not be handled by the MvcHandler which means the ASP.NET framework would not generate the response for that request (the custom handler would generate this response)

HttpModules

HttpModules are classes that implement IHttpModule and are designed to respond to ASP.NET life cycle events

  • they can provide services to ASP.NET and the executing framework (e.g. MVC)
  • they can also be used to manipulate the request itself
  • many modules can act on a single request
  • each module can hook into many life cycle events
  • helps keep main project cleaner
    • http module code could be in Global.asax.cs
  • modules don't need to have application specific and can thus be reusable among a range of apps

Authentication is a common use of http modules

Creating an HttpModule

  • implement IHttpModule interface
    • exposes 2 members:
      1. Init()
        • called when a module is instantiated
        • used to register methods that will handle ASP.NET events
      2. Dispose()
        • release resources when module's work is done
  • register the HttpModule through code or config file
    • could streamline register via the PreApplicationStart method

HttpModules and HttpContext

  • modules can populate properties on HttpContext for later access by handlers (e.g. by MvcHandler)
    • e.g. Windows authentication module populates the HttpContext.User property
  • modules can act on the request before the mvc framework can
    • this enables modules to increase efficiency in situations where it could make sense to avoid the MVC framework execution entirely
    • example: redirects (demo showed use of a module to handle redirects from old to new URLs)
      • the module took control of the request and redirected to a new URL
      • the MVC framework wasn't involved until the second request (to the redirect destination URL)
        • the module redirected the first request to a new URL before MVC framework began executing
        • MVC framework only executed on second request to the redirect destination URL

note: demo showed interesting used of configuration classes working with web.config values

Comparing HttpModules and HttpHandlers

  • many modules can service one request, but only one handler can service a request
  • modules modify/support requests through services, handlers generate the response
  • both are implemented through simple interfaces IHttpModule and IHttpHandler
  • both can be registered through code or the web.config file
  • modules can integrate with any life cycle event, handlers only integrate with events related to handler selection and handler execution

MvcRouteHandler and MvcHandler (routing and handler selection)

On PostResolveRequestCache the UrlRoutingModule

  1. Matches the URL to a Route defined in the MVC app
  2. Gets the associated MvcRouteHandler (not an HttpHandler)
  3. Calls GetHttpHandler on MvcRouteHandler
  4. Calls ProcessRequest on returned MvcHandler

Understanding Controller Initialization

Discussion points

  • Controllers and the request pipeline
  • working with the controller factory
  • understanding the dependency resolver

Understanding Controllers and the life cycle

Controller concepts

  • the controller mediates between the models and views (OOP side note: GRASP.Indirection)
  • the controller implements IController and thus implements the Execute() method
    • this method is fairly generic
      • the MVC framework provides powerful features within this execution path
      • but we could have a custom controller that implements this method simply to write plain text as the response, or to work with XML in some specific fashion
    • controllers inheriting from the MVC framework-provided abstract Controller class enable all the functionality (complex or simple) that is typically needed though

Controller Initialization

As stated earlier, most of the functionality provided by the MVC framework occurs within the MVCHandler.ProcessRequest() method

  • this method is called between the two "RequestHandlerExecute" ASP.NET life cycle events

MVCHandler.ProcessRequest()

  1. MVCHandler.ProcessRequest() calls a child method ProcessRequestInit() to get a controller
    • ProcessRequestInit() asks a controller factory for a controller
      • the factory chooses the right controller based on the route data in the request context
      • the factory uses a controller activator (IControllerActivator) to create an instance of the needed controller and returns this controller
      • the activator uses a dependency resolver (this allows extensibility and DI)
        • if the dependency resolver can't create the controller, the activator will try to manually
  2. MVCHandler.ProcessRequest() calls Controller.Execute()

mvc-request-life-cycle-controller-initialization

The Controller Factory

Purpose: to provide the appropriate type of controller to service the request

  • implements IControllerFactory interface
    • normally if you need to customize the factory, you would inherit from the default controller factory rather than directly implement this interface
    • this interface has 3 methods
      1. IController CreateController(RequestContext requestContext, string controllerName)
        • the most important method; selects and returns the controller
      2. SessionStateBehavior GetControllerSessionStateBehavior(requestContext requestContext, string controllerName)
        • determines how session is handled for the provided controller
      3. void ReleaseController(IController controller)
        • releases any resources the factory is holding onto

Features and Conventions

  • the controller factory uses conventions to determine the controller by the URL via name matching
  • the controller factory uses a dependency resolver which enables DI

The Dependency Resolver

This is used by the MVC Framework internally to resolve many different types (not limited to providing controller instances)

The IDependencyResolver interface

  • exposes 2 methods
    1. Object GetService(Type serviceType)
      • this is the resolver method that the controller factory calls to obtain an instance of the correct type of controller
      • if this method for some reason returns null, the controller factory directly uses the System.Activator class to attempt to get an instance of the desired type
    2. IEnumerable<object> GetServices(Type serviceType)
  • since the dependency resolver is used to resolve many different types (not just controller types), it doesn't know what type it will be returning which is why it returns Object
    • the controller factory casts this result to IController

Default Dependency Resolver Parameterless Constructor issue

  • The MVC Framework's default dependency resolver doesn't support creating controllers via a constructor with parameters--it only supports creating controllers via their parameterless constructor
    • the unfortunate side-effect is of course that it makes constructor DI into the controller impossible
  • But MVC Extensibility to the rescue!
    • since the MVC framework abstracts this instantiation functionality into an interface (IDependencyResolver), we have the ability to inject a different resolver into the controller factory that supports creating controllers via a constructor with parameters
    • the Ninject IoC includes functionality that does this
      • (just one of many example implementations)

Action Method Execution

Topics

  • action methods in the life cycle
  • action invoker
  • making decisions with action selectors (attributes)
  • influencing the request with Filters
  • the model binding process

The Action Method Execution process

mvc-request-life-cycle-action-method-execution-process

The action invoker is used by a controller to locate and execute an action method.

  • implements IActionInvoker which has one method InvokeAction(ControllerContext controllerContext, string actionName)
  • the MVC Framework's default action invoker ControllerActionInvoker is very powerful and extensible, so implementing a custom action invoker is rarely needed

Selectors

The action invoker uses selectors to choose the right action method to execute

  • familiar example selectors: [HttpGet], [HttpPost], [ActioName]
  • eligible controller methods must be public, and non-static, and non-special (e.g. ctor, .tostring, etc.)
  • action method selection considers the action name and selectors
  • if there's only one action method with a name matching the URL segment action name, this action method is selected
  • if there is more than one, the selectors are considered

Custom action selectors can be created by writing a class that inherits from the ActionMethodSelectorAttribute class

  • can override an IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) method
  • any candidate action methods with a selector where this method returns false are removed from consideration
  • in the event that 2 action methods match but only one has a selector and this selector returns true, the action method with the selector is prioritized
    • e.g.: for a GET request, 2 matching methods, one has [HttpGet]--this method is selected

The Model Binding Process

Model binding is the process that maps data from the request to parameters on the selected action method.

  • model binders implement IModelBinder interface
  • model binders use Value Providers
    • these search different sources such as forms, route data, query string, etc. and provide the discovered values to the model binders
    • can create custom value providers

Understanding Filters

  • scope: can be applied at the action method, controller, or global level
  • run before (OnActionExecuting()) and after (OnActionExecuted()) the action method is executed
    • can specify order in which they execute before the action method
      • they execute in order of most global to most specific
      • otherwise they execute in any order (or the specified order)
    • whatever order they run, they are executed in reverse order after the action method
  • there are a few different kinds of filters (not noting these--easy to find this info)
  • not noting how to create custom filters--easy to find this info

Action Results and the View Engine

Topics

  • action results execution in the life cycle
  • building a custom action result
  • view results and the view engine
  • extending view engine functionality

Action Result execution process

  • action result execution is triggered by the action invoker
  • before the action result executes, the associated result filters are run first
    • result filters are almost conceptually identical to action results
    • OnResultExecuting and OnResultExecuted (before and after action result is executed)
  • Action Result's ExecuteResult() method executes
    • there is a base action result class that that declares this execute result method
      • mvc provides different types of action results by inheriting from this base and providing different implementations of this method
  • there are 2 branches of execution when ExecuteResult() executes
    1. View (or partial view) Result
      • view engine locates and renders the view
    2. Other action results
      • the action result itself writes the response
  • Result Filters' OnResultExecuted methods are run
    • this is generally the last extensibility point in the MVC framework

mvc-request-life-cycle-action-result-execution-process

Common action result types

All of these inherit from the base action result class and have their own implementation of the ExecuteResult() method.

  • ContentResult, JsonResult, EmptyResult, ViewResult, FileResult, RedirectResult

Creating a custom ActionResult

  • the built-in JsonResult class uses the .NET JS serializer class
  • demo creates a custom ActionResult that returns JSON like the built-in JsonResult class, but uses the Json.NET serializer instead
    • create new class that inherits from ActionResult
    • override ExecuteResult(ControllerContext context)

View Results and the view engine

The most notable difference about view results that distinguishes them from action results is their use of a view engine.

A view engine is a special class responsible for finding and rendering views.

2 Default view engines are available in the MVC framework

  1. Razor View Engine
  2. Legacy View Engine (uses a syntax from the Web Forms days)

This course is generally concerned with the view engine's role in the request pipeline.

The ViewResult Execution Process

  • ActionInvoker calls ExecuteResult()
  • in this scenario the type of action result is a ViewResult, so the ViewResultBase class's ExecuteResult() is called
  • within ExecuteResult() ViewResultBase.FindView() asks the view engine to find the View
    • this FindView() method is abstract, so the derived ViewResult class implements this
  • the view engine returns the found view
  • ViewResultBase tells the View to render itself (View.Render())

mvc-request-life-cycle-viewresult-execution-process

Can have multiple view engines registered

  • each view engine will search for the view in the order that they are registered
  • once a view is found, the search is done, and any view engines later in the order will not run
  • can edit the oder of view engine execution in Global.asax.cs with
    • ViewEngines.Engines.Insert(index, new MyCustomViewEngine())
  • can customize where a view engine searches for views
⚠️ **GitHub.com Fallback** ⚠️