In my initial days of learning MVC, I was curious about it's life cycle. But I found it a bit confusing. So I have documented my understandings. I hope this may help some one. This is a pure conceptual thing. To understand this, one needs to have a solid understanding of OOP concepts (especially about classes, interfaces, abstraction, inheritances and so on).
What happens in a normal ASP.NET application?
- In an ASP.NET application each ASP.NET page inherits from System.Web.UI.Page that implements the IHTTPHandler interface.
- This interface has an abstract method ProcessRequest() and hence is implemented in the Page class. This method is called when you request a page.
- The ProcessRequest() method takes an instance of HttpContext and is responsible for processing the request and generating the response.
So in an ASP.NET application it is so straight forward, you request a page with an URL like http://mysite/default.aspx. Then, ASP.NET searches for that page on the disk, executes the ProcessRequest() method, generates and renders the response. There is a one-to-one mapping between URL and physical page.
The ASP.NET MVC Process
In a MVC application, no physical page exists for a specific request. All the requests are routed to a special class called the Controller. The controller is responsible for generating the response and sending the content back to the browser. Also, there is a many-to-one mapping between URL and controller.
When you request a MVC application, you are directly calling the action method of a controller.
When you request http://mysite/Controller1/method1, you are actually calling Controller1's method1. We will see how our request is routing to an ActionMethod of a controller.
The procedure involved is:
The ASP.NET MVC Process
In a MVC application, no physical page exists for a specific request. All the requests are routed to a special class called the Controller. The controller is responsible for generating the response and sending the content back to the browser. Also, there is a many-to-one mapping between URL and controller.
When you request a MVC application, you are directly calling the action method of a controller.
When you request http://mysite/Controller1/method1, you are actually calling Controller1's method1. We will see how our request is routing to an ActionMethod of a controller.
The procedure involved is:
- An instance of the RouteTable class is created on application start. This happens only once when the application is requested for the first time.
- The UrlRoutingModule intercepts each request, finds a matching RouteData from a RouteTable and instantiates a MVCHandler (an HttpHandler).
- The MVCHandler creates a DefaultControllerFactory (you can create your own controller factory also). It processes the RequestContext and gets a specific controller (from the controllers you have written). Creates a ControllerContext. Passes the controller a ControllerContext and executes the controller.
- Gets the ActionMethod from the RouteData based on the URL. The Controller Class then builds a list of parameters (to pass to the ActionMethod) from the request.
- The ActionMethod returns an instance of a class inherited from the ActionResult class and the View Engine renders a view as a web page.
Now, let's understand in detail
- Every ASP.NET MVC application has a RouteTable class. This RouteTable is responsible for mapping the MVC requests to a specific controller's ActionMethod.
Whenever the application starts, the Application_Start event will be fired and it will call the RegisterRoutes() with the collection of all the available routes of the MVC application as a parameter that will add the routes to the Routes property of the System.Web.Routing.RouteTable class. The Routes property is of type RouteCollection.
Global.asax- public class MvcApplication : System.Web.HttpApplication
- {
- protected void Application_Start()
- {
- WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables:true);
- AreaRegistration.RegisterAllAreas();
- WebApiConfig.Register(GlobalConfiguration.Configuration);
- FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
- RouteConfig.RegisterRoutes(RouteTable.Routes);
- BundleConfig.RegisterBundles(BundleTable.Bundles);
- }
- }
- public class RouteConfig
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // Ignore route ending with axd
- routes.MapRoute(
- name: "Default", // route name
- url: "{controller}/{action}/{id}", // Url pattern
- defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } // create a default route
- );
- }
- }
Now, all the mapped routes are stored as a RouteCollection in the Routes property of the RouteTable class.
NOTE: The RouteTable class has a Routes property that holds a collection of objects that derive from theRouteBase class. The RouteCollection class is derived from Collection<RouteBase>. Hence RegisterRoutes() is taking an object of RouteCollection.
When an ASP.NET MVC application handles a request, the application iterates through the collection of routes in the Routes property to find the route that matches the format of the URL requested. The application uses the first route that it finds in the collection that matches the URL. So the most specific route should be added first then the general ones. - Whenever you request an ASP.NET MVC application, the request is intercepted by the UrlRoutingModule (an HTTP Module) and:
- The UrlRoutingModule wraps up the current HttpContext (including the URL, form parameters, query string parameters and cookies associated with the current request) in an HttpContextWrapper object as below.
NOTE: The HttpContext class has no base class and isn't virtual and hence is unusable for testing (it cannot be mocked). The HttpContextBase class is a (from C# 3.5) replacement to HttpContext. Since it is abstract, it is mockable. It is concretely implemented by HttpContextWrapper. To create an instance of HttpContextBase in a normal web page, we use:
New HttpContextWrapper(HttpContext.Current).- private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
- {
- HttpApplication application = (HttpApplication) sender;
- HttpContextBase context = new HttpContextWrapper(application.Context);
- this.PostResolveRequestCache(context);
- }
- Based on the HttpBaseContext object, the postResolveRequestCache() will return the correct RouteData from the RouteTable (that was created in the previous Step #1).
- If the UrlRoutingModule successfully retrieves a RouteData object then it creates a RequestContext object that represents the current HttpContext and RouteData.
- The UrlRoutingModule then instantiates a new HttpHandler based on the RouteTable and passes the RequestContext (created in Step c) to the new handler's constructor.
- For an ASP.NET MVC application, the handler returned from the RouteTable will always be an MvcHandler. This MVCHandler implements an IHTTPHandler interface and hence the ProcessRequest() method.
- Finally, it will call the RemapHandler() method that 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 == 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.CurrentUICulture,
- SR.GetString("UrlRoutingModule_NoHttpHandler"),
- new object[] { routeHandler.GetType() }));
- }
- if (httpHandler == UrlAuthFailureHandler)
- {
- if (!FormsAuthenticationModule.FormsAuthRequired)
- {
- throw new HttpException(0x191,
- SR.GetString("Assess_Denied_Description3"));
- }
- UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current,this);
- }
- else
- {
- context.RemapHandler(httpHandler);
- }
- }
- }
- }
- The UrlRoutingModule wraps up the current HttpContext (including the URL, form parameters, query string parameters and cookies associated with the current request) in an HttpContextWrapper object as below.
- MVCHandler is also inherited from the IHTTPAsyncHandler hence implements the ProcessRequest() method. When MVCHandler executes, it calls the ProcessRequest() method that in turn calls the ProcessRequestInit() method.
The ProcessRequestInit() method creates a ControllerFactory and a Controller. The Controller is created from a ControllerFactory. There is a ControllerBuilder class that will set the ControllerFactory.
NOTE: By default it will be DefaultControllerFactory. But you can create your own ControllerFactory as well.
By implementing the IControllerFactory interface and then adding the following code to the Application_Start event in the gloabal.asax.
ControllerBuilder.Current.SetControllerFactory(typeof(NewFactory))
Now, the NewFactory will be used instead of the DefaultControllerFactory .
The RequestContext and the name of the Contoller (from the URL) will be passed to CreateController() method to get the specific Contoller (that you have written).
Next, a ControllerContext object is constructed from the RequestContext and the controller using the method GetContollerInstance().- private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
- {
- 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));
- }
- }
- 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);
- }
- Our Controller inherits from the Controller class that inherits from ControllerBase that implements the Icontroller interface. The Icontroller interface has an Execute() abstract method that is implemented in the ControllerBase class.
- public 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();
- //Other stuffs here
- }
- The ViewBag, ViewData, TempData and so on properties of the ControllerBase class is initialized. These properties are used for passing data from the View to the Controller or vice-versa or among action methods.
- The Execute() method of the ControllerBase class is executed that calls the ExecuteCore() abstract method. ExecuteCore() is implemented in the Controller class.
- protected override void ExecuteCore()
- {
- PossiblyLoadTempData();
- try
- {
- string actionName = RouteData.GetRequiredString("action");
- if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
- {
- HandleUnknownAction(actionName);
- }
- }
- finally
- {
- PossiblySaveTempData();
- }
- }
- The ExecuteCore() method gets the Action name from the RouteData based on the URL.
- The ExecuteCore() method then calls the InvokeAction() method of the ActionInvoker class. This builds a list of parameters from the request. This list of parameters are passed as method parameters to the ActionMethod that is executed. Here the Descriptor objects viz.ControllerDescriptor and ActionDescriptor, that provide information on the controller (like name, type, actions) and Action (name, parameter and controller) respectively play a major role. Now you have your Controller name and Action name.
This controller class is something that you wrote. So one of the methods that you wrote for your controller class is invoked.
NOTE: controller methods that are decorated with the [NonAction] attribute will never be executed.
Finally It will call the InvokeAction method to execute the Action.- 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;
- }
- The Controller returns an instance of ActionResult. The Controller typically executes one of the helper methods (mostly View() that returns an instance of the ViewResult class, that is derived from the ActionResult class). Here's the list of classes that extend from the ActionResult class. You just need to call a specific Helper method to return the respective ActionResult.
Action Result Helper Method Description ViewResult View Renders a view as a Web page. PartialViewResult PartialView Renders a partial view, that defines a section of a view that can be rendered inside another view. RedirectResult Redirect Redirects to another action method by using its URL. RedirectToRouteResult RedirectToAction
RedirectToRouteRedirects to another action method. ContentResult Content Returns a user-defined content type. JsonResult Json Returns a serialized JSON object. JavaScriptResult JavaScript Returns a script that can be executed on the client. FileResult File Returns binary output to write to the response. EmptyResult (None) Represents a return value that is used if the action method must return a null result (void).
[Courtesy: Controllers and Action Methods in ASP.NET MVC Applications ]- public abstract class ActionResult
- {
- public abstract void ExecuteResult(ControllerContext context);
- }
The following happens after the ExecuteResult() method of ViewResult is called.- ViewResultBase calls the FindView() method of the ViewResult class.
- The FindView() method of the ViewResult class returns an instance of the ViewEngineResult class.
- The Render() method of the ViewEngineResult class is called to Render the view using the ViewEngine.
- The Render() method internally calls the RenderViewPage() method that sets the master page location and ViewData.
- The response is rendered on client browser.
- public virtual ViewEngineResult FindView( ControllerContext controllerContext, string viewName, string masterName)
- {
- if (controllerContext == null)
- {
- throw new ArgumentNullException("ControllerContext");
- }
- if (string.IsNullOrEmpty(viewName))
- {
- throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
- }
- Func<IViewEngine, ViewEngineResult> cacheLocator = e => e.FindView(controllerContext, viewName, masterName, true);
- Func<IViewEngine, ViewEngineResult> locator = e => e.FindView(controllerContext, viewName, masterName, false);
- return 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 = (ViewPage) obj2;
- if (page != null)
- {
- this.RenderViewPage(viewContext, page);
- }
- else
- {
- ViewUserControl control = (ViewUserControl) obj2;
- if (control == null)
- {
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_WrongViewBase, new object[] { this. ViewPath }));
- }
- this.RenderViewUserControl(viewContext, control);
- }
- }
- private void RenderViewPage(ViewContext context, ViewPage page)
- {
- if (!string.IsNullOrEmpty(this.MasterPath))
- {
- page.MasterLocation = this.MasterPath;
- }
- page.ViewData = context.ViewData;
- page.RenderView(context);
- }
The RenderView() method finally calls the ProcessRequest() method of the Page class that renders the view in the client browser.
No comments :
Post a Comment