Introduction
Whenever a request is received by MVC, it is the job of the routing engine to match the request URL with the registered routes. After finding the matched route, the route handler for the route is being called. Each route can have its own route handler. A route handler is a class that implements IRouteHandler
interface.
IRouteHandler Interface
It defines a contract that a class must implement in order to process a request for a matching route pattern. It exposes a single method, the GetHttpHandler()
method, which is responsible for providing the route handler class instance that will process the request. This how the interface looks like:
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
Any route that is added through the MapRoute
extension method is handled by the default route handler, that is the MvcRouteHandler
class defined in the System.Web.Mvc
namespace.
MvcRouteHandler Class
It is the responsibility of the route handler to determine the HTTP handler that will serve the request, by looking at the received RequestContext
. The MvcRouteHandler
class implementation, as shown here:
namespace System.Web.Mvc
{
using System.Web.Routing;
using System.Web.SessionState;
public class MvcRouteHandler : IRouteHandler {
private IControllerFactory _controllerFactory;
public MvcRouteHandler() {
}
public MvcRouteHandler(IControllerFactory controllerFactory) {
_controllerFactory = controllerFactory;
}
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
requestContext.HttpContext.SetSessionStateBehavior(
GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
protected virtual SessionStateBehavior
GetSessionStateBehavior(RequestContext requestContext) {
string controllerName =
(string)requestContext.RouteData.Values["controller"];
IControllerFactory controllerFactory = _controllerFactory ??
ControllerBuilder.Current.GetControllerFactory();
return controllerFactory.GetControllerSessionBehavior
(requestContext, controllerName);
}
#region IRouteHandler Members
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
return GetHttpHandler(requestContext);
}
#endregion
}
}
It’s straightforward to see that, in the given preceding code, the class implements IRouteHandler
interface's GetHttpHandler()
method, which does two things: first, it makes a call to the SetSessionStateBehavior()
method, for the current Http request, in order to set the session state support that is needed to handle the request, by passing a SessionStateBehavior
enumeration value to specify what type of session state behavior applies to the request. In order to get the session state behavior, a call is made to the GetSessionStateBehavior()
method, which gets the controller name and finds the controller factory using the ControllerBuilder
class. The ControllerBuilder
class returns a reference of DefaultControllerFactory
class (which is a type of IControllerFactory
interface, and under the assumption that we don't have any custom controller factory). The DefaultControllerFactory
class GetControllerSessionBehavior()
method is called, passing the current RequestContext
and the controller type, which in turn returns a SessionStateBehavior
enumeration value. Now, after setting the SessionStateBehavior
, it proceeds with the creation of an instance of MvcHandler
class, which is the default ASP.NET MVC HTTP handler, to serve the request.
The MvcHandler Class
It is the responsibility of MvcHandler
class for generating the response for the ongoing request being processed. The MvcHandler
class receives information about the ongoing request from the RequestContext
passed to its constructor, in the implementation of the GetHttpHandler()
method in the MvcRouteHandler
class. The MvcHandler
class implements three interfaces : IHttpAsyncHandler
, IHttpHandler
and IRequiresSessionState
.
IRequiresSessionState
interface, which when implemented specifies that the current HTTP handler requires read and write access to session-state values. This is a marker interface and has no methods in it, to implement.
IHttpHandler
interface defines the contract that a class must implement in order to synchronously process HTTP Web requests using HTTP handler. It exposes a single method, i.e., the ProcessRequest()
method. IHttpAsyncHandler
interface is the asynchronous version of IHttpHandler
interface.
This is how the MvcHandler
class implements IHttpHandler
.
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);
}
});
}
The ProcessRequest()
method converts the HttpContext
into a more generic container—the HttpContextWrapper
class. HttpContextWrapper
class is a wrapper for HttpContext
and extends HttpContextBase
class. Now the question is why are we using HttpContextWrapper
instead of HttpContext
. The reason is that the HttpContext
class has no base class and it's not virtual also, so it's not possible to use it in unit testing. Hence, we use HttpContextBase
class, which is abstract
and consists of the same members as the HttpContext
class. The HttpContextBase
class enables one to create derived classes that are similar to the HttpContext
class, that can be used for unit testing.
After getting HttpContextBase
a call is made to the internal version of ProcessRequest()
method, passing HttpContextBase
parameter to it. The call is then further to ProcessRequestInit()
, which is responsible for extracting controllers name from RouteData
collection and request controller factory to create corresponding controller.
internal static readonly string MvcVersion = GetMvcVersionString();
public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version";
internal ControllerBuilder ControllerBuilder {
get {
if (_controllerBuilder == null) {
_controllerBuilder = ControllerBuilder.Current;
}
return _controllerBuilder;
}
set {
_controllerBuilder = value;
}
}
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();
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}
protected internal virtual void AddVersionHeader(HttpContextBase httpContext) {
if (!DisableMvcResponseHeader) {
httpContext.Response.AppendHeader(MvcVersionHeaderName, MvcVersion);
}
}
private static string GetMvcVersionString() {
return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2);
}
The ProcessRequestInit()
, receives the HttpContextBase
object containing Http specific information and references of IController
and IControllerFactory
type, which will be holding the created copies of controller and controller factory objects at the end. It starts with, adding the header information to the response.
Then, in order to get the controller factory, which is responsible for the creation of instances of any controller class, the ControllerBuilder
class is used. The reference to ControllerBuilder
property in ProcessRequestInit()
method, it returns an instance of the DefaultControllerFactory
class, if there is no custom controller factory. After getting the instance of the controller factory, it is then used to instantiate a controller, by making a call to CreateController()
method of the DefaultControllerFactory
class. You can find more about DefaultControllerFactory
class over here.
After getting the instance of the controller, the Execute()
method is called for this controller. The execute
method is responsible for invoking the action
method whose name matches the action
value in the route data. To know more about the working of Execute()
method, refer to my previous post.
After the completion of the Execute()
method, the ReleaseController()
method is called, which is present in the DefaultControllerFactory
class, in order to dispose the controller instance.
ControllerBuilder Class
The ControllerBuilder
is a singleton class responsible for producing an instance of type IControllerFactory
. The ControllerBuillder
class has two constructors, a default one and another parametrized one.
private Func<IControllerFactory> _factoryThunk = () => null;
private static ControllerBuilder _instance = new ControllerBuilder();
private IResolver<IControllerFactory> _serviceResolver;
public ControllerBuilder()
: this(null) {
}
internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) {
_serviceResolver = serviceResolver ??
new SingleServiceResolver<IControllerFactory>(
() => _factoryThunk(),
new DefaultControllerFactory { ControllerBuilder = this },
"ControllerBuilder.GetControllerFactory"
);
}
public static ControllerBuilder Current {
get {
return _instance;
}
}
[SuppressMessage("Microsoft.Design",
"CA1024:UsePropertiesWhereAppropriate", Justification =
"Calling method multiple times might return different objects.")]
public IControllerFactory GetControllerFactory() {
return _serviceResolver.Current;
}
public HashSet<string> DefaultNamespaces {
get {
return _namespaces;
}
}
The default constructor calls constructor with parameter, passing null
as argument, saying that SingleServiceResolver
object has to be used as default service resolver, which gets saved in the _serviceResolver
. Now whenever we need a reference to the ControllerBuilder
object, we get it through the Current
property. In order to get the created instance of the DefaultControllerFactory
class outside this class, the GetControllerFactory()
method is used.
ControllerBuilder
class also exposes DefaultNamespaces
property which is used to define the default namespaces at the start of the application. Whenever default namespaces are provided using this property, DefaultControllerFactory
uses these namespaces to find a controller type.
Apart from the above implementations, ControllerBuilder
class also exposes SetControllerFactory()
method which acts as a setter method and allows you to change default controller factory with custom one.