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 a http request to a method invocation with arguments? Wizardry does not exist, so we will try here to describe everything that happens during the MVC request processing. How and why everything is done? What can we extend?… A little trip deep into the framework always useful to find some pretty solutions to recurring problems.
We won’t explain here dependency injection (although it’s very helpful in some cases) it adds useless complexity for the life cycle understanding. You just have to know that all the interfaces representing extension points can be resolved very easily by the dependency injection system.
The first system to handle the request is routing. It analyzes the request depending on the context and chooses which controller and which action has to be called. In an MVC application, the goal of routing is to create a route data dictionary containing at least 2 entries: “controller” and “action” (+ “area” if needed). Those 2 entries will allow the correct controller to be instantiated and the correct action to be called during the next steps.
Routing is an http module: System.Web.Routing.UrlRoutingModule handling the cached routing results and a http handler System.Web.Routing.UrlRoutingHandler handling the request itself. It relies on the route table defined in the singleton RouteTable. Each route contains a constraint dictionary specifying valid values for the url parameters, a DataTokens dictionary which allows us to store custom values and a default values dictionary containing the defualt values for each url parameter. It’s in this last dictionary where we will find the names ot the controller and action to invoke.
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: MVCRouteHandler. It is possible to use another handler by not using MapRoute and creating directly a route object with the desired handler. This will have to implement the IRouteHandler interface. The default handler does nothing but defining the session state behavior and to hand over the request to another handler: the MvcHandler.
This takes us to the next step: creating the controller.
Creation of the Controller
At this point, we have a RequestContext object containing enough data to find which controller will be instantiated. The MvcHandler relies by default on a DefaultControllerFactory object which knows how to create a controller depending on the RequestContext. It is possible to extend this factory by implementing the IControllerFactory interface and declaring it in the Application_Start this way:
This technique allows us to define common properties for every controller in a more elegant way than using a base controller.
The ControllerFactory contains a CreateController method which returns a controller instance from a string. Our controller is finally ready! We now need to invoke the right action: it’s the goal of the ActionInvoker object.
Invoking the Action
At this point, there is still a lot to do before retrieving the result. The ActionInvoker orchestrates the remaining process. It has a big responsibility going from finding the action and handling errors to filters and result processing. The IActionInvoker interface which it implements is although very simple and defines only one InvokeAction method. The default type used in MVC is ControllerActionInvoker and is affected to the controller by the CreateActionInvoker() method. This method is protected and so overridable by your own controller implementations if needed. The methods of the ControllerActionInvoker class are also overridable.
The first interesting method of this class is FindAction: it returns an ActionDescriptor object representing the action to be executed. At this point, we know which action we will call.
The next step consists in getting all the filters applying to this action. There are 4 filter types:
authorization filters (AuthorizationFilters), error filters (ExceptionFilters), action filters (ActionFilters) and result filters (ResultFilters).
The first filters to be invoked are authorization filter: if the user is not allowed to call this action, the processing stops here and sends a 403 error.
If the user is authorized, the hard work can now begin!
Action arguments mapping
If the action has arguments, we need to set them with a value. In this goal, the ActionInvoker uses a ModelBinder object. The application contains a ModelBinder collection each one associated with one or more types. Each ModelBinder implements the IModelBinder interface with one method:
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
This method is called for each argument on it’s corresponding ModelBinder. For your own types, you can implement your own ModelBinder and add it to the collection in the Application_Start via:
ModelBinders.Binders.Add(typeof (MyType), new MyModelBinder())
You don’t have to do this for all your types but only those with special bindings to apply.
The ModelBinder instantiates an object and fill the properties but where does it find values? The answer is found in the 2nd argument of BindModel. The ModelBindingContext object contains a ValueProvider object.
The application contains a collection of ValueProviderFactory objects which goal is to instantiate IValueProvider implementations. These objects are the link between the elements of the http request (query string, form,…) and the object we are currently binding.Let’s imagine the action has an argument called “id”. The ModelBinder will try to find its value and will ask the ValueProviders if they can identify it (for example does “id” exists in the form? does the query string contains the “id” key? is there a file “id” uploaded?). The ValueProviders are asked sequentially and the first to answer will give the value of the argument. This call is made by:
There are a few built-in ValuProviderFactory:
You also can create your own and add them to the collection by adding in the Application_Start:
This can be really useful, I recently had to create a CookieValueProvider which avoids a lot of cookie code in the actions for example.
Executing the action
Once all of this is done, the ActionInvoker calls the action execution. It begins with th OnActionExecuting method of the ActionFilters, then the action itself and finally the OnActionExecuted method of ActionFilters. Nothing particular to not here it’s just standard execution. In the end, we get an ActionResult object containing the result of the action.
The result of the action can be of any type deriving from ActionResult. There are a lot of them like the ViewResult which is used to send a page to the user. The principle of all results is the same: they contain an ExecuteResult method used to get the final result which is sent back to the client. In th same way as ActionFilters, ResultFilters are used here before and after ExecuteResult. Let’s get a deeper look into ViewResult and the ViewEngine it uses to render a page.
ViewResult and ViewEngine
The ViewResult is the one used to process a razor view and send the resulting html in the response. It relies for this on a ViewEngine. The process has several steps:
First, the ViewResult asks the ViewEngine for the view via the FindView method. This returns a VienEngineResult containing an IView implementation. IView contains the Render method which processes the view and writes the result in a TextWriter directly linked to the Response by the ViewResult object. As the ViewEnginResult used is directly created by the ViewEngine, the Render method is the right ViewEngine’s render method. This way, the ViewResult does not depend on the ViewEngine and we can use any ViewEngine without changing anything else. The most famous is the RazorViewEngine but you can also create your own as I demonstrated it in my french blog (a post demonstrating how to use the new ASP.Net mobile view switching directly in MVC3 using a custom ViewEngine. translation to come…)
It’s the end of our little trip deep into MVC. I will zoom on each part with real world extensibility examples soon.
For the courageous people who are still reading, Thanks :)