Understanding of MVC Page Life Cycle

When you invoke an action in an MVC application, everything happens as if you were simply calling a method in the controller class. How do we go from an http request to a method invocation with arguments? I will try here to describe everything that happens during the MVC request processing. How and why everything is done? A little trip deep into the framework always useful to find some pretty solutions to recurring problems.

Let’s look at each steps in detail. I have put some code as well to understand a little better how everything works in MVC.

Step 1: Routing

In ASP.NET application each asp.net page implements the IHTTPHandler interface.

This interface has a ProcessRequest() method that gets called when you request the page. The ProcessRequest() method is responsible for processing the request and generating the response. So in asp.net application it is simple, you request for a page in the url like https://mysite1\default.aspx and then it search for that page on the disk and execute the processrequest method and generate the response.

However in MVC application it doesn’t work in that way. There is no physical page exist for a particular request. All the requests are routed to a special class called Controller. The controller is responsible for generating the response and sending the content back to the browser.

When you build a MVC application you can define a bunch of controllers. Each controller can handle multiple requests. For example, all the following will route to same Controller.

https://mysite1/Controller1/1

https://mysite1/Controller1/2

https://mysite1/Controller1/3

These requests will execute the same Controller Controller1 and in that it will extract the id from the URL and process the request. This gives more readable URL then asp.net page.

So how these requests are routed to particular controller. Now the RouteTable come into the picture. Every ASP.NET MVC application has a RouteTable. This routetable is responsible to map the mvc requests to particular controller.

public class MvcApplication : System.Web.HttpApplication

{

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

);

}

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

}

}

}

Whenever the Application Start the Application_Start events will get called and it will call the method RegisterRoutes which will send the collection of Routes as the parameter and the routes will be added to this Collection which is a Dictionary of NamedMaps.

Each route also references a handler which will handle the rest of the request. When we use MapRoute, what we really do is creating a route by instantiating the 3 dictionaries and defining the default MVC handler: PageRouteHandler. Each RouteHandler implements an interface IRouteHandler.

public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults,   RouteValueDictionary constraints, RouteValueDictionary dataTokens)

{

if (routeUrl == null)

{

throw new ArgumentNullException("routeUrl");

}

Route item = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));

this.Add(routeName, item);

return item;

}

Step 2: The URL Routing Module intercepts the request.

Whenever you make a request against an ASP.NET MVC application, the request is intercepted by the UrlRoutingModule HTTP Module. 

When the UrlRoutingModule intercepts a request, the first thing the module does is to wrap up the current HttpContext in an HttpContextWrapper object.

The HttpContextWrapper object derives from HTTPContextBase class.

 

private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)

{

HttpApplication application = (HttpApplication) sender;

HttpContextBase context = new HttpContextWrapper(application.Context);

this.PostResolveRequestCache(context);

}

Then the module send this contextBase object to the method called PostResolveRequestCache .

Based on the Context it will return the correct RouteData from the RouteTable which was created in the earlier step based on the URL, form parameters and query string parameters associated with the HTTPContext object. 

If the UrlRoutingModule successfully retrieves a RouteData object then the module next creates a RouteContext object that represents the current HttpContext and RouteData.

The module then instantiates a new HttpHandler based on the RouteTable and passes the RouteContext to the new handler’s constructor. 

In the case of an ASP.NET MVC application,the handler returned from the RouteTable will always be an MvcHandler. This MVCHandler also derive from interface IHTTPHandler and implement the method ProcessRequest().

The last step is it will call the RemapHandler method which will set the MVCHandler just obtained to be the Current HTTP Handler.

 

public virtual void PostResolveRequestCache(HttpContextBase context)

{

RouteData routeData = this.RouteCollection.GetRouteData(context);

if (routeData != null)

{

IRouteHandler routeHandler = routeData.RouteHandler;

if (routeHandler == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));

}

if (!(routeHandler is StopRoutingHandler))

{

RequestContext requestContext = new RequestContext(context, routeData);

context.Request.RequestContext = requestContext;

IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

if (httpHandler == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICultureSR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));

}

if (httpHandler is UrlAuthFailureHandler)

{

if (!FormsAuthenticationModule.FormsAuthRequired)

{

throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));

}

UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);

}

else

{

context.RemapHandler(httpHandler);

}

}

}

}

 

Step 4: MVC Handler Executes

MVCHandler also inherit from the IHTTPAsyncHandler. When MVC Handler executes it will call the BeginProcessRequest method of the httpAsyncHandler asynchronously.

When the process request method is called a new controller gets created. The controller is created from a ControllerFactory. There is a ControllerBuilder Class which will set the ControllerFactory. 

You can create your own ControllerFactory as well but by default it will be DefaultControllerFactory. The RequestContext and the name of the Contoller will be passed to the method CreateController Method to get the particular Contoller.

public virtual IController CreateController(RequestContext requestContext, string controllerName)

{

if (requestContext == null)

{

throw new ArgumentNullException("requestContext");

}

if (string.IsNullOrEmpty(controllerName))

{

throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");

}

Type controllerType = this.GetControllerType(requestContext, controllerName);

return this.GetControllerInstance(requestContext, controllerType);

}

 

Next, a ControllerContext object is constructed from the RequestContext and the controller by using the method GetContollerInstance.

Finally, the Execute() method is called on the controller class. The ControllerContext is passed to the Execute() method when the Execute() method is called.

 

Step 4: The Controller Executes

The Execute() method starts by creating the TempData object. TempData is a dictionary derived from TempDataDictionary class and stored in short lives session and it is a string key and object value. 

The Execute() method gets the Action from the RouteData based on the URL.The Controller Class then call the ContollerActionInvoker that builds a list of parameters from the request.

These parameters, extracted from the request parameters, will act as method parameters.The parameters will be passed to whatever controller method gets executed.

Finally It will call the InvokeAction method to execute the Action.

protected override void ExecuteCore()

{

this.PossiblyLoadTempData();

try

{

string requiredString = this.RouteData.GetRequiredString("action");

if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString))

{

this.HandleUnknownAction(requiredString);

}

}

finally

{

this.PossiblySaveTempData();

}

}

public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)

{

if (controllerContext == null)

{

throw new ArgumentNullException("controllerContext");

}

if (string.IsNullOrEmpty(actionName))

{

throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");

}

ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext);

ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);

if (actionDescriptor == null)

{

return false;

}

FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);

try

{

AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext,  filters.AuthorizationFilters, actionDescriptor);

if (context.Result != null)

{

this.InvokeActionResult(controllerContext, context.Result);

}

else

{

if (controllerContext.Controller.ValidateRequest)

{

ValidateRequest(controllerContext);

}

IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext,actionDescriptor);

ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext,filters.ActionFilters, actionDescriptor, parameterValues);

this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result);

}

}

catch (ThreadAbortException)

{

throw;

}

catch (Exception exception)

{

ExceptionContext context3 = this.InvokeExceptionFilters(controllerContext,filters.ExceptionFilters, exception);

if (!context3.ExceptionHandled)

{

throw;

}

this.InvokeActionResult(controllerContext, context3.Result);

}

return true;

}

 

Step 5: The Render View Method is called

The Controller typically either executes either the RedirectToAction Method or the RenderView Method. When you call a controller’s RenderView() method,the call is delegated to the current ViewEngine’s RenderView() method.

 The WebFormViewEngine.RenderView() method uses a class named the ViewLocator class to find the view. Next, it uses a BuildManager to create an instance of a ViewPage class from its path.

Next, if the page has a master page, the location of the master page is set If the page has ViewData, the ViewData is set. Finally, the RenderView() method is called on the ViewPage.

 protected override IView CreateView(ControllerContext controllerContext, 
                             string viewPath, string masterPath)
 {
     return new WebFormView(viewPath, masterPath);
 }

public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)

{

<>c__DisplayClass6 class2;

if (controllerContext == null)

{

throw new ArgumentNullException("controllerContext");

}

if (string.IsNullOrEmpty(viewName))

{

throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");

}

Func<IViewEngine, ViewEngineResult> cacheLocator =  new Func<IViewEngine, ViewEngineResult>(class2, (IntPtr) this.<FindView>b__4);

Func<IViewEngine, ViewEngineResult> locator =  new Func<IViewEngine, ViewEngineResult>(class2, (IntPtr) this.<FindView>b__5);

return this.Find(cacheLocator, locator);

}

public virtual void Render(ViewContext viewContext, TextWriter writer)

{

if (viewContext == null)

{

throw new ArgumentNullException("viewContext");

}

object obj2 = this.BuildManager.CreateInstanceFromVirtualPath(this.ViewPath, typeof(object));

if (obj2 == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,

                                        MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,

                                          new object[] { this.ViewPath }));

}

ViewPage page = obj2 as ViewPage;

if (page != null)

{

this.RenderViewPage(viewContext, page);

}

else

{

ViewUserControl control = obj2 as ViewUserControl;

if (control == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,

                                          MvcResources.WebFormViewEngine_WrongViewBase,

                                                 new object[] { this.ViewPath }));

}

this.RenderViewUserControl(viewContext, control);

}

}

 

The ViewPage class derives from the base System.Web.UI.Page class. This is the same class that is used for pages in classic ASP.NET.

The final action that RenderView() method performs is to call ProcessRequest() on the page class. Calling ProcessRequest() generates content from the view in the same way that

content is generated from a normal ASP.NET page.

private void RenderViewPage(ViewContext context, ViewPage page)

{

if (!string.IsNullOrEmpty(this.MasterPath))

{

page.MasterLocation = this.MasterPath;

}

page.ViewData = context.ViewData;

page.RenderView(context);

}

public virtual void Render(ViewContext viewContext, TextWriter writer)

{

if (viewContext == null)

{

throw new ArgumentNullException("viewContext");

}

object obj2 = this.BuildManager.CreateInstanceFromVirtualPath(this.ViewPath, typeof(object));

if (obj2 == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,

                                MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,

                                      new object[] { this.ViewPath }));

}

ViewPage page = obj2 as ViewPage;

if (page != null)

{

this.RenderViewPage(viewContext, page);

}

else

{

ViewUserControl control = obj2 as ViewUserControl;

if (control == null)

{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,

                                MvcResources.WebFormViewEngine_WrongViewBase,

                                           new object[] { this.ViewPath }));

}

this.RenderViewUserControl(viewContext, control);

}

}

Hope this post helps in understanding the MVC Life Cycle.