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

Одним из ключевых классов в ASP.NET MVC  является класс ControllerBuilder, который не очень часто упоминаестя, но является очень важным звеном в цепочке конвейера обработки запросов. Напомню, что в предыдущей статье мы начали рассматривать фабрику контроллеров конвейера обработки запросов ASP.NET MVC. Хотя, судя по его названию, нельзя сказать, что он имеет прямое отношение к фабрике контроллеров. Но если учитывать то, что именно он создаёт экземпляр фабрики контроллеров, а последняя в свою очередь непосредственно создаёт сами экземпляры контроллеров, то вроде всё встаёт на свои места. Что же представляет из себя данный тип? А вот что:
namespace System.Web.Mvc
{
  public class ControllerBuilder
  {
    private static ControllerBuilder _instance = new ControllerBuilder();
    private Func<IControllerFactory> _factoryThunk = () => null;
    private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
    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; }
    }

    public HashSet<string> DefaultNamespaces
    {
      get { return _namespaces; }
    }

    [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", 
      Justification = "Calling method multiple times might return different objects.")]
    public IControllerFactory GetControllerFactory()
    {
      return _serviceResolver.Current;
    }

    public void SetControllerFactory(IControllerFactory controllerFactory)
    {
      if(controllerFactory == null)
      {
        throw new ArgumentNullException("controllerFactory");
      }

      _factoryThunk = () => controllerFactory;
    }

    public void SetControllerFactory(Type controllerFactoryType)
    {
      if(controllerFactoryType == null)
      {
        throw new ArgumentNullException("controllerFactoryType");
      }
      if(!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType))
      {
        throw new ArgumentException(
            String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.ControllerBuilder_MissingIControllerFactory,
                controllerFactoryType),
            "controllerFactoryType");
      }

      _factoryThunk = delegate
      {
        try
        {
          return (IControllerFactory)Activator.CreateInstance(controllerFactoryType);
        }
        catch(Exception ex)
        {
          throw new InvalidOperationException(
              String.Format(
                  CultureInfo.CurrentCulture,
                  MvcResources.ControllerBuilder_ErrorCreatingControllerFactory,
                  controllerFactoryType),
              ex);
        }
      };
    }
  }
}
Класс ControllerBuilder реализует паттерн одиночка (Singletone). Т.е. для всего домена приложения (Application Domain), а как известно к одному домену привязано одно приложение ASP.NET, он будет единственным. Естественно ни о какой отложенной инициализации единственного экземпляра ControllerBuilder речи нет, поскольку он востребован всегда, почти при каждой обработке запросов. Как видно из кода, у данного класса два конструктора уровня экземпляра. Причём первый конструктор используется для создания объекта по умолчанию и по цепочке вызывает второй передавая папаметр null.
public ControllerBuilder()
  : this(null)
{
}

internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
{
  _serviceResolver = serviceResolver ?? 
    new SingleServiceResolver<IControllerFactory>(
                                       () => _factoryThunk(),
                                       new DefaultControllerFactory { ControllerBuilder = this },
                                       "ControllerBuilder.GetControllerFactory");
} 
Для разрешения зависимостей в ASP.NET MVC, начиная с третьей версии, повсеместно используется паттерн локатор служб (Service locator). Многие называют его антипаттерном, и я не могу сказать, что они не правы. Реализован он в виде интерфейса.
internal interface IResolver<T>
{
    T Current { get; }
} 
Поскольку по умолчанию в конструктор ничего не передаётся (т.е. передаётся null), то в игру вступает дефолтовый локатор служб ASP.NET MVC, а именно - SingleServiceResolver. Он вернёт объект фабрики типа DefaultControllerFactory, который тоже является типом по умолчанию для фабрики в ASP.NET MVC. А с какого места становится востебованным ControllerBuilder? В самом обработчике маршрута MvcRouteHandler, когда впервые возникает необходимость в экземпляре фабрики контроллеров.
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);
}
Если быть более точным, то при задании поведения состояния сеанса (SessionState). Помимо всего этого у класса есть метод SetControllerFactory с единственной перегрузкой,
public void SetControllerFactory(IControllerFactory controllerFactory);
public void SetControllerFactory(Type controllerFactoryType);
посредством которого можно задать использование фабрики контроллеров отличной от предлагаемой в ASP.NET MVC фабрики - DefaultControllerFactory. Он может принимать экземпляр фабрики контроллеров, который должен реализовать интерфейс IControllerFactory или же объект представляющий тип фабрики (System.Type controllerFactoryType) активируемый посредством рефлексии. Использовать метод SetControllerFactory, как можно догадаться, нужно для регистрации собственной фабрики контроллеров в глобальном классе приложения (в файле Global.asax).
public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory);
  }
} 
По сути, данная возможность является одной из основных для расширения или изменения стандартного поведения конвейера обработки запросов. А практическое применение данной возможности будет показано в отдельных статьях. Одним из таковых и очень важных применений является реализация паттерна инверсия зависимостей (Inversion of Control). И наконец свойство
public HashSet<string> DefaultNamespaces
{
  get { return _namespaces; }
}
используется для задания приоритетных пространств имён для поиска экземпляров контроллеров в первую очередь в них. Причём установка этих имён имеет приоритет перед теми именами которые были заданы в системе маршрутизации. Делается это всё в том же глобальном коде приложения.
protected void Application_Start()
{
  ControllerBuilder.Current.DefaultNamespaces.Add("MyNamespace”);
}
На этом всё, а в следующей статье будет рассмотрен стандартный класс DefaultControll­erFactory, который как уже известно представляет фабрику контроллеров по умолчанию в ASP.NET MVC.