📜 ⬆️ ⬇️

Request Life Cycle in the MVC 3 Framework Application

Any developer knows that the URL in MVC routing plays a key role. As you know, there is a standard routing:

routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional ); 


It is the rule by which routes are chosen. In the simplest cases, you can swap the action and controller, or add many more parameters that will appear in the url as follows: param1 / param2 / param3 /. Sometimes, for example in CMS systems, it is necessary to make part of the url come as a variable.
')
 routes.MapRoute( "Default", "{controller}/{action}/{*url}", new { controller = "Content", action = "Home", url = UrlParameter.Optional }); 


This rule allocates Action and Controller, and assigns the rest of the variable url.
Routes are registered when the application starts. Namely, in the Global.asax file in the Application_Start () method.

To create most applications of this knowledge is enough. The goal of my article is to go through the entire Request life cycle and see what happens before the application parses the url and transfers control to the desired action.

1. Create the MVC request handler.
MVC includes the following handler types: (Based on MSDN)
• MvcHandler. This handler is responsible for initiating the ASP.NET pipeline for the MVC application. It receives an instance of Controller from the MVC controller factory; This controller performs further processing of the request. Note that although MvcHandler implements IHttpHandler, it cannot be mapped as a handler (for example, for the .mvc file name extension), because the class does not support a no-parameter constructor. (The only constructor requires a RequestContext object).
• MvcRouteHandler. This class implements IRouteHandler, so it can be integrated with ASP.NET routing. The MvcRouteHandler class associates a route with an MvcHandler instance. An instance of MvcRouteHandler is registered with routing, using the MapRoute method. When you call the MvcRouteHandler class, the class creates an instance of MvcHandler using the current RequestContext instance. It then passes the control to the new MvcHandler instance.
• MvcHttpHandler. This handler is used to simplify the direct mapping of the handler bypassing the routing module. This is useful when you need to map a file name extension, such as .mvc, directly to an MVC handler. Internally, MvcHttpHandler performs the same tasks as regular ASP.NET routing (passing through MvcRouteHandler and MvcHandler). However, it performs these tasks as a handler, not a module. This handler is usually not used if UrlRoutingModule is enabled for all requests.

What can it be needed for?

The general view of the MvcRouteHandler override is as follows:

 public class CustomRouteHandler : MvcRouteHandler { protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { //TODO: something return base.GetHttpHandler(requestContext); } } 


You need to initialize CustomRouteHandler in the Global class Application_Start method ()

 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); var route = routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); route.RouteHandler = new CustomRouteHandler(); 


2. Selection and creation of a controller
The ControllrerFactory class is responsible for selecting and creating the controller. This class implements the IControllerFactory interface.

  public interface IControllerFactory { IController CreateController(RequestContext requestContext, string controllerName); SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); void ReleaseController(IController controller); } 


Naturally, you can create your own controller factory by implementing this interface, but as a rule, to meet your needs, it is enough to override the methods of the DefaultControllerFactory class. Typically, this is an IController CreateController (RequestContext requestContext, string controllerName). Below is an example of a redefined controller factory.

  public class MyControllerFactory : DefaultControllerFactory { public override IController CreateController(RequestContext requestContext, string controllerName) { //TODO: something Type controllerType = this.GetControllerType(requestContext, controllerName); return this.GetControllerInstance(requestContext, controllerType); } public override void ReleaseController(IController controller) { //TODO: something var disposable = controller as IDisposable; if (disposable != null) { disposable.Dispose(); } } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { //TODO: something try { return (IController)Activator.CreateInstance(controllerType); } catch (Exception exception) { throw new InvalidOperationException(); } } } 


By the way, Autofac, Ninject, Unity all create their own controller factory for Dependency Injection.
Registering your own factory, also happens in the Global class, Application_Start () method
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());

3. Action
After creating the controller instance, the IController.Execute () method is called. In the Execute () method, we need to perform the actions we need. In the standard Controller class, control is transferred to ActionInvoker, he is responsible for calling a specific action.

 public class CustomActionInvoker : IActionInvoker { public bool InvokeAction(ControllerContext context, string actionName) { //TODO: invoke Action // if action was found // return true return false; } } 


You can register it in the controller by initializing it in the constructor.
 public class CustomActionInvokerController : Controller { public CustomActionInvokerController() { this.ActionInvoker = new CustomActionInvoker(); } } 


Before calling, ActionInvoker checks Action attributes derived from ActionMethodSelectorAttribute and FilterAttribute (AuthorizeAttribute, HandleErrorAttibute, ValidateAntiForgeryTokenAttribute, ValidateInputAttribute) before calling. These attributes allow you to decide whether to transfer control to one or another action.
To create your own action selectors - inherit from this attribute

 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public abstract class ActionMethodSelectorAttribute : Attribute { public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo); } 


4. Binders
As we already know, the MVC mechanism allows, besides the simplest parameters (int, string), to use more complex, for example, classes. But sometimes you need to take, quite an exotic class, with which the standard binder can not cope. In this case, we can write our own binder. To do this, it is enough to inherit from DefaultModelBinder and override its methods.

 public class CustomModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //TODO: something return base.BindModel(controllerContext, bindingContext); } protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { //TODO: something return base.CreateModel(controllerContext, bindingContext, modelType); } protected override System.ComponentModel.PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { //TODO: something return base.GetModelProperties(controllerContext, bindingContext); } protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) { //TODO: something base.BindProperty(controllerContext, bindingContext, propertyDescriptor); } protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } protected override ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext) { //TODO: something return base.GetTypeDescriptor(controllerContext, bindingContext); } protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { base.OnModelUpdated(controllerContext, bindingContext); } protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext) { //TODO: something return base.OnModelUpdating(controllerContext, bindingContext); } protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value); } protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { //TODO: something return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value); } protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { //TODO: something base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value); } } 


As you can see, the base class provides us with a lot of opportunities.
You can connect your ModelBinder directly to the model. With an attribute.

 [ModelBinder(typeof(CustomModelBinder))] 


Or in the Global class, the Application_Start () method.

 ModelBinders.Binders.Add(typeof(CustomTypeModel), new CustomModelBinder()); 


5 ActionResult
Finally, after the action has completed, on returns ActionResult (). This can be View (), PartialView (), JsonResult (), FileResult (), EmptyResult ().
Naturally, this list can be expanded and supplemented with our implementations of ActionResult (). For this, it suffices to inherit from the ActionResult class, and override the void method ExecuteResult (ControllerContext context)

 public class CustomResult : ActionResult { public override void ExecuteResult(ControllerContext context) { //TODO: something } } 


There is also an interesting way to add the necessary methods to the View () and use them directly from the * .cshtml file. For this we inherit from the class WebViewPage.

 public abstract class CustomView : WebViewPage { public string HelloWordPrint() { return "Hello Word"; } } 


In the very RazorView, we use the following directive

 @inherits CustomView 


Now you can use new methods in View.

Conclusion
That's all. In this post, we went through all the stages of Request in MVC, we looked at how we can influence each stage. I hope now you have a complete picture of how the MVC3 framework works.

Source: https://habr.com/ru/post/136834/


All Articles