Внутри ASP.NET MVC: конвейер обработки запросов, часть третья (обработчик запроса)

В данной статье будет рассматриваться вторая часть конвейера обработки запросов, а именно - обработчик запросов, в ASP.NET MVC. Эта статья является продолжением данной и описывает более детально вторую часть конвейера, показанного тут. Предыдущую статью, мы завершили рассмотретнием обработчика маршрута и остановилисть на обработчике HTTP-данных, коим является MvcHandler, обрабатывающий запрос дальше. Ниже показана рассматриваемая в данной статье часть от общей схемы конвейера.

Как было сказано во второй части: после того как обработчик маршрута извлечёт обработчик HTP-данных, соответствующий текущему маршруту, послеледний продолжит обработку запроса дальше. Как он это делает было показано в предыдущей части, но возникает другой вопрос: откуда он берёт эти данные? Их задаём мы, в том же файле Global.asax, при старте приложения, вызывая метод RouteConfig.RegisterRoutes(RouteTable.Routes):
namespace MvcApplication
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
      );
    }
  }
}
Дело в том, что метод MapRoute принадлежит не типу RouteCollection, а является расширяющим и определён в сборке System.Web.Mvc, в классе RouteCollectionExtensions:
public static Route MapRoute(this RouteCollection routes, string name, 
          string url, object defaults, object constraints, string[] namespaces)
{        
    if (routes == null)        
    {        
        throw new ArgumentNullException("routes");        
    }        
    if (url == null)        
    {        
        throw new ArgumentNullException("url");        
    }        

    Route route = new Route(url, new MvcRouteHandler())        
    {        
        Defaults = CreateRouteValueDictionary(defaults),        
        Constraints = CreateRouteValueDictionary(constraints),        
        DataTokens = new RouteValueDictionary()        
    };        

    if ((namespaces != null) && (namespaces.Length > 0))        
    {        
        route.DataTokens["Namespaces"] = namespaces;        
    }        

    routes.Add(name, route);        

    return route;        
}        
Высше приведена наиболее полная версия метода, остальные версии перегружены и в конечном счёте используют эту реализацию. Получается, что метод MapRoute не принадлежит системе маршрутизации, а является частью ASP.NET MVC. Подобным образом работает и расширяющий метод MapHttpRoute, который принадлежит Web API, и задает свой обработчик маршрута (думаю не будет лишним ещё раз упомянуть, что мы можем реализовать свой обработчик маршрута, как показано тут, который даёт нам полную свободу действий). Как видно из кода обработчиком маршрута, при создании объекта Route, задается MvcRouteHandler, в коде которого и прописан рассматриваемый нами обработчик MvcHandler:
namespace System.Web.Mvc
{
  public class MvcRouteHandler : IRouteHandler
  {
    //...........................................
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
      requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
      return new MvcHandler(requestContext);
    }
    //...........................................
  }
} 
Экземпляр обработчика запроса MvcHandler создаётся в методе PostResolveRequestCache модуля маршрутизации (полный код метода показан в предыдущей статье)
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
на основе данных извлечённых, в том же методе PostResolveRequestCache, из текущего маршрута.
IRouteHandler routeHandler = routeData.RouteHandler;
//.................
RequestContext requestContext = new RequestContext(context, routeData);
//................. 
context.Request.RequestContext = requestContext;
Прежде чем идти дальше, остановимся и рассмотрим класс MvcHandler более подробно. Он определён в пространстве имен System.Web.Mvc следующим образом:
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
отсюда уже ясно, что это не только синхронный, но и асинхронный обработчик HTTP-данных. Именно в качестве последнего, асинхронного обработчика, и выступает MvcHandler при обработке запроса в интегрированном режиме конвейера сервиса IIS 7.x и высше. У него есть только один конструктор
public MvcHandler(RequestContext requestContext)
{        
    if (requestContext == null)        
    {        
        throw new ArgumentNullException("requestContext");        
    }        

    RequestContext = requestContext;        
}
единственная задача которого инициализировать доступное только для чтения свойство RequestContext текущим контекстом запроса. Высше было показано где создаётся контекст запроса типа RequestContext, а передаётся он в виртуальный метод обработчика маршрута.
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{    
  requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));    
  return new MvcHandler(requestContext);    
}       
Там же и создаётся экземпляр нашего обработчика MvcHandler. Контекст запроса типа RequestContext является обёрткой содержащей контекст запроса типа HttpContextBase (передаваемый в метод PostResolveRequestCache) и данные маршута RouteData. Позже он будет использоваться контроллером и не только. Продолжим продвигаться дальше. После того, как был создан экземпляр MvcHandler происходит сопоставление его текущему запросу, что и делает метод
// Remap IIS7 to our handler 
context.RemapHandler(httpHandler);
вызываемый в том же методе PostResolveRequestCache (на самом деле сопоставление происходит не напрямую, а с использование внутреннего поля _remapHandler класса HttpContext, который позже присваивается свойству Handler в классе HttpApplication). Использование MvcHandler начинается во внутреннем методе IExecutionStep.Execute() класса HttpApplication. В этом методе устанавливаются две ссылки:
HttpContext context = _application.Context;
IHttpHandler handler = context.Handler; 
и начинается проверка объекта обработчика. После проверки на равенство null идёт проверка на предмет того, является ли обработчик асинхронным:
else if (handler is IHttpAsyncHandler)
, а так как это именно так и есть, дальше обработка запроса выполняется асинхронно. Если это не так, запрос будет обработан синхроннно (например примером синхронного обработчика является обычная страница Page из Web Forms). Здесь и происходит вызов метода BeginProcessRequest
protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, 
          AsyncCallback callback, object state)
{        
    HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);        
    return BeginProcessRequest(httpContextBase, callback, state);        
}
, котрый вызывает перегруженный одноименный метод
protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, 
          AsyncCallback callback, object state)
{        
    IController controller;        
    IControllerFactory factory;        
    ProcessRequestInit(httpContext, out controller, out factory);
	//.................
}
в коде которого и начинается использование фабрики контроллеров и последующая часть обработки запроса. Но об этом уже в следующей статье. Ниже показана иллюстрация схемы вызовов методов, которая была описана в данной статье.