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

В предыдущей статье был рассмотрен один из ключевых классов относящихся к третьей условной части конвейера ASP.NET MVC, а именно - ControllerBuilder. Не менее важным в этой цепочке является также класс DefaultControllerFactory, который предсталяет фабрику контроллеров по умолчанию. Напомню, что в первой статье четвёртой части, данного цикла, мы остановились на следующем куске кода.
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
Первая строка которой была рассмотрена, как я уже упомянул выше, в предыдущей статье. Что из себя представляет данный тип? Класс, который реализует следующий интерфейс.
public interface IControllerFactory
{
  IController CreateController(RequestContext requestContext, string controllerName);
  SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, 
    string controllerName);
  void ReleaseController(IController controller);
}
Абстагирование процесса инстацирования экземпляров контроллеров, даёт большую гибкость при манипуляции процессом обработки запросов в конвейере. И эта возможность является одной из основных точек раширения, как я уже отмечал раньше. Как можно догадаться, всё это не что иное, как реализация шаблона проектирования "Фабрика (Factory)". Самым важным методом фабрики является метод CreateController, делающий основную работу. Класс DefaultControllerFactory реализует его следующим образом.
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 = GetControllerType(requestContext, controllerName);
  IController controller = GetControllerInstance(requestContext, controllerType);
  return controller;
}
Из вышеприведённого кода нам интересны следующие две строки.
Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = GetControllerInstance(requestContext, controllerType);
Первая получает объект типа Type, представляющий тип контроллера который будет обрабатывать запрос. Тут всё основано на соглашениях. Данные об имени контроллера берутся из таблицы маршрутизации:
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
 
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
, в методе ProcessRequestInit обработчика HTTP-данных MvcHandler. Затем они передаются в метод GetControllerType, который так же принадлежит классу DefaultControllerFactory. Последний анализирует нужные пространства имен и в случае успеха возращает нужный экзепляр типа Type. Код данного метода приведён ниже.
protected internal virtual Type GetControllerType(RequestContext requestContext, 
  string controllerName)
{
  if(String.IsNullOrEmpty(controllerName))
  {
    throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
  }
 
  object routeNamespacesObj;
  Type match;
  if(requestContext != null && 
    requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj))
  {
    IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
    if(routeNamespaces != null && routeNamespaces.Any())
    {
      HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, 
        StringComparer.OrdinalIgnoreCase);
      match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, 
        controllerName, namespaceHash);
 
      if(match != null || 
        false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"]))
      {
        return match;
      }
    }
  }
 
  if(ControllerBuilder.DefaultNamespaces.Count > 0)
  {
    HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, 
      StringComparer.OrdinalIgnoreCase);
    match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, 
      controllerName, namespaceDefaults);
    if(match != null)
    {
      return match;
    }
  }
 
  return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null);
}
А возращает данный метод результат метода GetControllerTypeWithinNamespaces.
private Type GetControllerTypeWithinNamespaces(RouteBase route, 
  string controllerName, HashSet<string> namespaces)
{
  // Once the master list of controllers has been created we can quickly index into it
  ControllerTypeCache.EnsureInitialized(BuildManager);
 
  ICollection<Type> matchingTypes = 
    ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
  switch(matchingTypes.Count)
  {
    case 0:
      // no matching types
      return null;
 
    case 1:
      // single matching type
      return matchingTypes.First();
 
    default:
      // multiple matching types
      throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);
  }
}
После того как получен экземпляр типа контроллера, уже возможно содать экземпляр самого контроллера. Это и делает метод GetControllerInstance.
protected internal virtual IController GetControllerInstance(RequestContext requestContext,
  Type controllerType)
{
  if(controllerType == null)
  {
    throw new HttpException(404,
                            String.Format(
                                CultureInfo.CurrentCulture,
                                MvcResources.DefaultControllerFactory_NoControllerFound,
                                requestContext.HttpContext.Request.Path));
  }
  if(!typeof(IController).IsAssignableFrom(controllerType))
  {
    throw new ArgumentException(
        String.Format(
            CultureInfo.CurrentCulture,
            MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
            controllerType),
        "controllerType");
  }
  return ControllerActivator.Create(requestContext, controllerType);
}
Но он не выполняет эту работу непосредственно, а делегирует создание объекта контроллера другому классу. Этим классом явялется тип реализующий интерфейс IControllerActivator.
public interface IControllerActivator
{
    IController Create(RequestContext requestContext, Type controllerType);
}
В случае с фабрикой по умолчанию им является закрытый класс DefaultControllerActivator
private class DefaultControllerActivator : IControllerActivator
{
  private Func<IDependencyResolver> _resolverThunk;
 
  public DefaultControllerActivator()
    : this(null)
  {
  }
 
  public DefaultControllerActivator(IDependencyResolver resolver)
  {
    if(resolver == null)
    {
      _resolverThunk = () => DependencyResolver.Current;
    }
    else
    {
      _resolverThunk = () => resolver;
    }
  }
 
  public IController Create(RequestContext requestContext, Type controllerType)
  {
    try
    {
      return (IController)(_resolverThunk().GetService(controllerType) 
        ?? Activator.CreateInstance(controllerType));
    }
    catch(Exception ex)
    {
      throw new InvalidOperationException(
          String.Format(
              CultureInfo.CurrentCulture,
              MvcResources.DefaultControllerFactory_ErrorCreatingController,
              controllerType),
          ex);
    }
  }
}
, который находится внутри самого типа DefaultControllerFactory. Как видно из кода,  ничего необычного в нём нет. Объект контроллера создаётся посредством рефлексии (отражения). После этого он готов к использованию. Осталось рассмотреть ещё два метода: GetControllerSessionBehavior и ReleaseController. Если вы внимательно читали данный цикл статей, то уже должны знать, что на ранней стадии обработки запроса в управляемом конвейере ASP.NET (естественно при использовании модуля маршрутизации) маршруту назначается так называемый обработчик маршрута. В случае с MVC - MvcRouteHandler. Прежде, чем назначить обработчик HTTP-данных запросу (если помните его основная задача именно это),  во внутреннем методе GetHttpHandler
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"];
  if (String.IsNullOrWhiteSpace(controllerName))
  {
    throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController);
  }
 
  IControllerFactory controllerFactory = _controllerFactory ?? 
    ControllerBuilder.Current.GetControllerFactory();
  return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
}
, а предоставляет эту работу фабрике. Последняя делает именно эту работу в своём методе GetControllerSessionBehavior.
protected internal virtual SessionStateBehavior 
  GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
{
  if(controllerType == null)
  {
    return SessionStateBehavior.Default;
  }
 
  return _sessionStateCache.GetOrAdd(
      controllerType,
      type =>
      {
        var attr = type.GetCustomAttributes(typeof(SessionStateAttribute), inherit: true)
            .OfType<SessionStateAttribute>()
            .FirstOrDefault();
 
        return (attr != null) ? attr.Behavior : SessionStateBehavior.Default;
      });
}
И наконец последний метод - ReleaseController. Он пробует освободить занятые контроллером ресурсы (если таковые имеются), то есть делает очистку.
public virtual void ReleaseController(IController controller)
{
  IDisposable disposable = controller as IDisposable;
  if(disposable != null)
  {
    disposable.Dispose();
  }
}
ReleaseController широко используется в классе обработчика HTTP-данных, которым является MvcHandler. На этом всё, в следующей статье будет рассмотрена последняя часть конвейера.