While programming with Asp.net MVC, you should be aware of the life of an Asp.net MVC request from birth to death. In this article, I am going to expose the Asp.net MVC Request Life cycle. There are seven main steps that happen when you make a request to an Asp.net MVC web applications. For more details refer Detailed ASP.NET MVC Pipeline
Routing
Asp.net Routing is the first step in MVC request cycle. Basically it is a pattern matching system that matches the request’s URL against the registered URL patterns in the Route Table. When a matching pattern found in the Route Table, the Routing engine forwards the request to the corresponding IRouteHandler for that request. The default one calls theMvcHandler
. The routing engine returns a 404 HTTP status code against that request if the patterns is not found in the Route Table.When application starts at first time, it registers one or more patterns to the Route Table to tell the routing system what to do with any requests that match these patterns. An application has only one Route Table and this is setup in the Global.asax file of the application.- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
- routes.MapRoute( "Default", // Route name
- "{controller}/{action}/{id}", // URL with parameters
- new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
- );
- }
MvcHandler
The MvcHandler is responsible for initiating the real processing inside ASP.NET MVC. MVC handler implements IHttpHandler interface and further process the request by usingProcessRequest
method as shown below:- protected internal virtual void ProcessRequest(HttpContextBase httpContext)
- {
- SecurityUtil.ProcessInApplicationTrust(delegate {
- IController controller;
- IControllerFactory factory;
- this.ProcessRequestInit(httpContext, out controller, out factory);
- try
- {
- controller.Execute(this.RequestContext);
- }
- finally
- {
- factory.ReleaseController(controller);
- }
- });
- }
Controller
As shown in above code, MvcHandler uses the IControllerFactory instance and tries to get a IController instance. If successful, the Execute method is called. The IControllerFactory could be the default controller factory or a custom factory initialized at theApplication_Start
event, as shown below:- protected void Application_Start()
- {
- AreaRegistration.RegisterAllAreas();
- RegisterRoutes(RouteTable.Routes);
- ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
- }
Action Execution
Once the controller has been instantiated, Controller's ActionInvoker determines which specific action to invoke on the controller. Action to be execute is chosen based on attributesActionNameSelectorAttribute
(by default method which have the same name as the action is chosen) andActionMethodSelectorAttribute
(If more than one method found, the correct one is chosen with the help of this attribute).View Result
The action method receives user input, prepares the appropriate response data, and then executes the result by returning a result type. The result type can be ViewResult, RedirectToRouteResult, RedirectResult, ContentResult, JsonResult, FileResult, and EmptyResult.View Engine
The first step in the execution of the View Result involves the selection of the appropriate View Engine to render the View Result. It is handled byIViewEngine
interface of the view engine. By default Asp.Net MVC usesWebForm
andRazor
view engines. You can also register your own custom view engine to your Asp.Net MVC application as shown below:- protected void Application_Start()
- {
- //Remove All View Engine including Webform and Razor
- ViewEngines.Engines.Clear();
- //Register Your Custom View Engine
- ViewEngines.Engines.Add(new CustomViewEngine());
- //Other code is removed for clarity
- }
View
Action method may returns a text string,a binary file or a Json formatted data. The most important Action Result is the ViewResult, which renders and returns an HTML page to the browser by using the current view engine.- IntroductionIn this article we will discuss about the MVC application lifecycle and how the request is processed as it passes from one component to the other. We will talk about the components in the order they occur in the application lifecycle .We will also look into the role of each of component and how they are related to the other component's in the pipeline.
Background
As developers we are aware about some of the components used by the MVC framework to process the request. Mostly we work with the controllers and the action methods.Also we work with the different ActionResult's and the Views . But are we aware about the other important components involved in the request processing?. And how the request flows in the request pipeline?When I started learning MVC one of the things I could not understand was how the request flows from one component to the other.Also I was not clear about the role of HTTP module and the HTTP handler in the request processing.After all MVC is a web development framework so there has to be HTTP module and the HTTP handler involved somewhere in the pipeline.We have more components involved in this request processing pipeline then we know of , the controller and the action methods that we work with, that have an equally important role in the request processing.Though most of the time we can use the default functionality provided by the framework but if we understand what is the role of each of the component we can easily swap the components or provide our own custom implementations.The main components and their role in the Request pipeline.Let us see what happen's when the request if first made for a resource in an MVC applicationThe entry to the Mvc applicationThe request is first intercepted by the UrlRoutingModule which is a HTTP Module. It is this module that decides whether the request would be handled by our MVC application. UrlRouting Module selects the first matching route.How does the UrlRoutingModule matches the request with the routes in the application?.If you look into the RegisterRoutes method called from the global.asax you will notice that we add routes to the routes RouteCollection. This method is called from the application_start event handler of the global.asaxIt is this RegisterRoutes method which registers all the routes in the applicationHide Copy CodeRouteConfig.RegisterRoutes(RouteTable.Routes); public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
Now you may ask how the UrlRouting Module knows about these routes and how the routes are associated with the RouteHandler? UrlRouting Module knows about these routes using the maproute method.If you look at the the maproute method you will notice that it is defined as an extension method.Behind the scenes it associates the routeHandler with the routes.Internally MapRoute method is implemented as:var Default = new Route(url, defaults , routeHandler);So basically what this method does is attach a routeHandler with the route.UrlRoutingModule is defined asHide Copy Codepublic class UrlRoutingModule : IHttpModule { public UrlRoutingModule(); public RouteCollection RouteCollection { get; set; } //omitting the other details
So now we know that the UrlRoutingModule is aware of all the routes in the application and hence it can match the correct route for the request.The main thing to note here is that the UrlRoutingModule selects the first matching route. As soon as a match is found in the routing table, scanning process stops.So let’s say we have 10 routes in our application and the more specific route is defined after the more general route so in this case the specific route that is added later will never be matched since the more general route will always be matched .So we need to take care of this when adding routes to the route collection.Here if the request is matched by any of the routes in route collection then the other routes added later in the collection will not be able to handle request.Please note that If the request is not matched by any of the routes in the UrlRoutingModule then it is not handled by the MvcApplication.So following happens at this stage.- The URLRoutingModule attaches the route handler to the routes.
RouteHandler
The generator of MvcHandlerAs we have already seen that MvcRouteHandler instance gets attached with the route's using the MapRoutemethod.MvcRouteHandler implements the IRouteHandler interface.This MvcRouteHandler object is used to obtain a reference to the MvcHandler object which is the HTTPHandler for our application.When the MvcRouteHandler is created one of the things it do is to call the PostResolveRequestCache() method.PostResolveRequestCache() method is defined asHide Copy Codepublic virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { IRouteHandler routeHandler = routeData.RouteHandler; IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
Following happens in the PostResolveRequestCache() method.
- RouteCollection porperty has a GetRouteData() method.This GetRouteData() method is called and passed theHttpContext.
- GetRouteData() method returns the RouteData object
- routeData has a RouteHandler property which returns the IRouteHandler for the current request which is the MvcRouteHandler.
- This MvcRouteHandler has the GetHttpHandler() method which returns the reference to the MvcHandler
- It then delegates control to the new MvcHandler instance.
MvcHandler
The Request ProcessorMvcHandler is defined asAs you can see that its a normal Http handler.Being an Http handler it implements the ProcessRequest() method. TheProcessRequest() method is defined as:Hide Copy Code// Copyright (c) Microsoft Open Technologies, Inc.<pre>// All rights reserved. See License.txt in the project root for license information. void IHttpHandler.ProcessRequest(HttpContext httpContext) { ProcessRequest(httpContext); } protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase iHttpContext = new HttpContextWrapper(httpContext); ProcessRequest(iHttpContext); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { SecurityUtil.ProcessInApplicationTrust(() => { IController controller; IControllerFactory factory; ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }); }
As you can see above the ProcessRequest() method calls the ProcessRequestInit() method which is defined as:Hide Copy Codeprivate void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { // If request validation has already been enabled, make it lazy. // This allows attributes like [HttpPost] (which looks // at Request.Form) to work correctly without triggering full validation. bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current); if (isRequestValidationEnabled == true) { ValidationUtility.EnableDynamicValidation(HttpContext.Current); } AddVersionHeader(httpContext); RemoveOptionalRoutingParameters(); // Get the controller type string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute factory = ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(RequestContext, controllerName); if (controller == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, factory.GetType(), controllerName)); } }
In the ProcessRequest() method following happens:- ProcessRequestInit() method is called which creates the ControllerFactory.
- This ControllerFactory creates the Controller.
- Controller's Execute() method is called
ControllerFactory
The generator of ControllerAs you can see above one of the things that happens inside the ProcessRequest() method is that ControllerFactory is obtained that is used to create the Controller object. Controller factory implements the interface IControllerFactory.By default the framework creates the DefaultControllerFactory type when the ControllerFactory is created using the ControllerBuilder.The ControllerBuilder is a singleton class and is used for creating the ControllerFactory.Following line in the ProcessRequestInit() method creates the ControllerFactory.Hide Copy Codefactory = ControllerBuilder.GetControllerFactory();
So the GetControllerFactory() method returns the ControllerFactory object. So now we have the ControllerFactory object.ControllerFactory uses the CreateController method to create the controller. CreateController is defined as:Hide Copy CodeIController CreateController( RequestContext requestContext, string controllerName )
The ControllerBase object is created using the default ControllerFactory implementation .If required we can extend this factory by implementing the IControllerFactory interface and then declaring the following in the Application_Start event in the gloabal.asax.Hide Copy CodeControllerBuilder.Current.SetDefaultControllerFactory(typeof(NewFactory))
The SetControllerFactory() method is used to set the custom controller factory instead of the Default Controller Factory that is used by the framework .The container for the user defined logicSo we have seen that the ControllerFactory creates the Controller object in theProcessRequest() method of the MvcHandler.As we know a controller contains the action methods.An action method gets called when we request a URL in the browser.Instead of explicitly implementing the IController interface we create our controllers using the Controllerclass which provides many features for us.Now this Controller class inherits from another Controller class called the "ControllerBase" which is defined as:Hide Copy Codepublic abstract class ControllerBase : IController { protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException( MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } VerifyExecuteCalledOnce(); Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { ExecuteCore(); } } protected abstract void ExecuteCore(); // .......
The controller object uses the ActionInvoker to call the action methods in the controller which we will look into later.After the controller object is created using the controller factory following happens :- The Execute() method of the controllerbase is called
- This Execute() method calls the ExecuteCore() method which is declared abstract and is defined by Controller class.
- The Controller class's implementation of the ExecuteCore() method retrieves the action name from the RouteData
- ExecuteCore() method calls ActionInvoker's InvokeAction() method.
ActionInvoker
The Action SelectorThe ActionInvoker class has some of the most important responsibilities of finding the action method in the controller and then invoking the action method.The ActionInvoker is an object of a type that implements the IActionInvoker interface. The IActionInvoker interface has a single method defined as:Hide Copy Codebool InvokeAction( ControllerContext controllerContext, string actionName )
The controller class provides the default implementation of IActionInvoker , which is ControllerActionInvokerThe controller class exposes a property named ActionInvoker which returns the ControllerActionInvoker .It uses the CreateActionInvoker() method to create the ControllerActionInvoker .If you see the method it is defined as virtual so we can override it and provide our own implementation to return a custom ActionInvoker.Hide Copy Codepublic IActionInvoker ActionInvoker { get { if (_actionInvoker == null) { _actionInvoker = CreateActionInvoker(); } return _actionInvoker; } set { _actionInvoker = value; } } protected virtual IActionInvoker CreateActionInvoker() { return new ControllerActionInvoker(); }
The ActionInvoker class needs to fetch the details of the action method and the controller to execute.These details are provided by the ControllerDescriptor .ControllerDescriptor and ActionDescriptor have an important role to play in the ActionInvoker.ControllerDescriptor is defined as "Encapsulates information that describes a controller, such as its name, type, and actions".ActionDescriptor is defined as "Provides information about an action method, such as its name, controller, parameters, attributes, and filters".One important method of the ActionDescriptor is "FindAction()".This method returns an ActionDescriptor object representing the action to be executed. So ActionInvoker knows which action to call.As we have seen above the ActionInvoker's InvokeAction() method is called in the ExecuteCore() method.Following happens when the ActionInvoker's InvokeAction() method is called- The ActionInvoker has to get the information about the controller and the action to perform. This information is provided by descriptor object's .The action and controller descriptor class's provides the name of the controller and the action.
- ActionMethod is Invoked
ActionResult
The command objectSo we have seen till now that the ActionMethod is called by the ActionInvoker.One of the characteristics of the action method is that instead of returning different data types it always returns the ActionResult type. ActionResult is an abstract class which is defined as:Hide Copy Codepublic abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
As the ExecuteResult() is an abstract method so different sub class's provides different implementations of theExecuteResult() method.One important thing to note is that an action result represents a command that the framework performs on behalf of the action method.As we know ActionMethods contains the logic that is executed and the result is returned to the client.Action methods themselves just return the ActionResult but don’t execute it.This ActionResult is executed and the response is returned back to the client .So ActionResult object represents the result that can be passed across the methods.Thus it separates the specification from the implementation as it represents the command object.For understanding commands in .NET please refer to the commands.There are specific ActionResult classes depending on the type of result we want to return like the Json or Redirection to another method.The "Controller" class that we use to inherit our controller class's provides many useful features that we can use out of the box.One such feature it provides is the methods which return's the specific types of ActionResult's . So instead of specifically creating the ActionResult object we can just call these methods.Below are some of these methods and the type of ActionResult they returnActionResult Class Helper Method ReturnType ViewResult View web page JsonResult Json Retuns a serialized JSON object RedirectResult Redirect Redirects to another action method ContentResult Content Returns a user-defined content type So we have seen till now that the ActionMethod is called by the ActionInvokerFollowing happens after the action method is invoked.- The OnActionExecuting methods of the ActionFilters are invoked.
- After this the action method itself is invoked.
- After the action method is invoked the OnActionExecuted methods of ActionFilters are invoked.
- The ActionResult is returned back from the ActionMethod
- The ExecuteResult() method of the ActionResult is called.
ViewEngine
The Renderer of the ViewViewResult is one of the most common return type used in almost all of the applications.It is used to render a view to the client using the ViewEngine. The view engine is responsible for generating
the HTML from viewWhen ViewResult is called by the action invoker it renders the view to the response by overriding the ExecuteResult method.The View engines provided by the framework are the Razor View Engine and Web Form
View Engine. But if you need some custom view engine for some custom functionality you can create a new view engine by implementing the IViewEngine interface which all the View Engine’s implement.The IViewEngine has the following methods:- FindPartialView The FindPartialView method is called when the Controller is looking to
return a Partial View with the given Name. - FindView The FindView method is called when Controller is
looking for a View with a given Name. - ReleaseView method is used for releasing the resources held by the
ViewEngine.
But instead of implementing these methods an easier way to create a view engine is to derive a new class from the abstract "VirtualPathProviderViewEngine" class.This class handles the low level details like finding the view's.So we have seen above the of the ActionResult is called. Since ViewResult is the most common type of ActionResult we will look into what happens if the the ExecuteResult() method of the ViewResult is called.There are two important classes ViewResultBase and ViewResult.ViewResultBase contains the following code that calls the FindViewMethod in ViewResultHide Copy Codeif (View == null) { result = FindView(context); //calls the ViewResult's FindView() method View = result.View; } ViewContext viewContext = new ViewContext(context, View, ViewData, TempData); View.Render(viewContext, context.HttpContext.Response.Output); protected abstract ViewEngineResult FindView(ControllerContext context); //this is implemented by //the ViewResult
Hide Copy Codeprotected override ViewEngineResult FindView(ControllerContext context) { ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } //rest of the code omitted }
Following happens after the ExecuteResult() method of ViewResult is called.- ExecuteResult of ViewResultBase is Invoked
- ViewResultBase calls the FindView of the ViewResult
- ViewResult returns the ViewEngineResult
- The Render() method of the ViewEngineResult is called to Render the view using the ViewEngine.
- The response is returned to the client.
Summary
If we have understanding of what is happening under the hood we are better able to understand the role of each component and how the different components are connected to each other.We have looked into some of the main Interfaces and classes used by the framework to handle the response. I believe this article will be helpful to you in understanding the internal details of the MVC application.Points of interest
One of the nice things about MVC is that all these components are loosely coupled.We can replace any of the component’s in the pipeline with another one.This gives much freedom to the developer. This means that each stage in the request pipeline we have the choice of the appropriate component to handle the request.We are free to provide our own implementation as well. This makes the applications more maintainable.It is because of this loosely coupled architecture that makes MVC applications are suitable for testing.We can easily provide the mock objects in place of the actual objects since there is no concrete class dependency.
No comments:
Post a Comment