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

Данная статья является последней и заключительной в цикле статей про конвейер обработки запросов ASP.NET MVC. В ней будет показана последняя часть конвейера. Напомню, что в предыдущей части, где была описана стандартная фабрика контроллеров, процесс создания экземпляров контроллеров был описан полностью. В этой статье мы рассмотрим, использование объекта контроллера и процесс дальнейшей обработки запроса и отправки ответа клиенту. И так, чем является объект контроллера? Это экземпляр типа, который должен быть экземпляром типа IController и иметь некий метод для выполнения определённой работы над контекстом запроса. Стандартное определение в библиотеке MVC такое.
namespace System.Web.Mvc
{
  public interface IController
  {
    void Execute(RequestContext requestContext);
  }
}
Достаточно иметь экземпляр типа, который реализует данный интерфейс, чтобы использовать его в качестве контроллера, для обработки запроса. Вот только писать такой тип с нуля, дело не простое. Поэтому существуют стандартные реализации типа IController. Первый тип это ControllerBase, который реализует часть функционала (полный код данного класса можно посмотреть в исходных кодах).
public abstract class ControllerBase : IController
{
  //...........................................................
 
  protected virtual void Execute(RequestContext requestContext)
  {
    if(requestContext == null)
    {
      throw new ArgumentNullException("requestContext");
    }
    if(requestContext.HttpContext == null)
    {
      throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext,
        "requestContext");
    }
 
    VerifyExecuteCalledOnce();
    Initialize(requestContext);
 
    using(ScopeStorage.CreateTransientScope())
    {
      ExecuteCore();
    }
  }
 
  protected abstract void ExecuteCore();
 
  protected virtual void Initialize(RequestContext requestContext)
  {
    ControllerContext = new ControllerContext(requestContext, this);
  }
 
  internal void VerifyExecuteCalledOnce()
  {
    if(!_executeWasCalledGate.TryEnter())
    {
      string message = String.Format(CultureInfo.CurrentCulture, 
        MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
      throw new InvalidOperationException(message);
    }
  }
 
  #region IController Members
 
  void IController.Execute(RequestContext requestContext)
  {
    Execute(requestContext);
  }
 
  #endregion
}
Второй тип, это класс Controller, являющийся наследником первого и базовым, для всех типов контроллеров, которые обычно создаются и используются в проектах.
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, 
  IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
{
  //...........................................................
}
Именно последний реализует большую часть стандартного функционала, для обработки запроса. Помимо того, что он наследуют фунционал ControllerBase, данный класс также реализует множество интерфейсов. Рассмотрим их по порядку. Реализация интерфейса IActionFilter
public interface IActionFilter
{
  void OnActionExecuting(ActionExecutingContext filterContext); 
  void OnActionExecuted(ActionExecutedContext filterContext);
}
, дополняет класс контроллера двумя методами. Первый метод выполняется перед вызовом метода действия, а второй – после вызова метода действия. А поскольку они реализованы как виртуальные методы, то можно переопределить их в своём типе с использованием некоторой логики. Похожая возможность описана в данной статье, правда с применением атрибутов. Метод интрефейса IAuthorizationFilter
public interface IAuthorizationFilter
{
  void OnAuthorization(AuthorizationContext filterContext);
}
выполняется самым первым из всех (первым из остальных методов фильтров и методов интерфейсов, которые выполняются перед запуском метода действия). Обычно он содержит логику авторизации для предоставления доступа или запрету доступа к методу действия. Третьим по счёту идёт стандатный интерфейс IDisposable из .NET. Реализация его единственного метода такая.
public void Dispose()
{
  Dispose(true /* disposing */);
  GC.SuppressFinalize(this);
}
 
protected virtual void Dispose(bool disposing)
{
}
Как видно из этого кода, вызов метода GC.SuppressFinalize препятствует вызову финализатора для данного типа (типа Controller) и даёт возможность реализовать часть функционала по очистке производному классу. Реализация интерфейса IExceptionFilter
public interface IExceptionFilter
{
  void OnException(ExceptionContext filterContext);
}
позволяет использовать метод OnException в произодном типе, чтобы реагировать на исключения. Пятый – интерфейс IResultFilter
public interface IResultFilter
{
  void OnResultExecuting(ResultExecutingContext filterContext);
  void OnResultExecuted(ResultExecutedContext filterContext);
}
предоставляющий методы, которые выполняются перед вызовом и после вызова метода результата действия. Предпоследний интрефейс IAsyncController
public interface IAsyncController : IController
{
  IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
  void EndExecute(IAsyncResult asyncResult);
}
реализован классом Controller и позволяет ему выступать в роли асинхронного контроллера в MVC 4. В качестве такового используется он в обработчике HTTP-данных MvcHandler. И последний – IAsyncManagerContainer
public interface IAsyncManagerContainer
{
  AsyncManager AsyncManager { get; }
}
, который выполняет роль счётчика для выполняющихся асинхронных операций. Настало время рассмотреть часть процесса обработки запроса контроллером. В этой статье мы дошли до описания этапа создания контроллера. Теперь рассмотрим его использование. Только заранее отмечу, что в MVC 4 используется асинхронная обработка запросов (если быть более точным, то APM) начиная с обработчика HTTP-данных MvcHandler. Собственно код, который приводился неоднократно в предыдущих статьях.
protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext,
  AsyncCallback callback, object state)
{
  IController controller;
  IControllerFactory factory;
  ProcessRequestInit(httpContext, out controller, out factory);
 
  IAsyncController asyncController = controller as IAsyncController;
  if(asyncController != null)
  {
    // asynchronous controller
    BeginInvokeDelegate beginDelegate =
      delegate(AsyncCallback asyncCallback, object asyncState)
    {
      try
      {
        return asyncController.BeginExecute(RequestContext, asyncCallback, asyncState);
      }
      catch
      {
        factory.ReleaseController(asyncController);
        throw;
      }
    };
 
    EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
    {
      try
      {
        asyncController.EndExecute(asyncResult);
      }
      finally
      {
        factory.ReleaseController(asyncController);
      }
    };
 
    SynchronizationContext syncContext = 
      SynchronizationContextUtil.GetSynchronizationContext();
    AsyncCallback newCallback = 
      AsyncUtil.WrapCallbackForSynchronizedExecution(callback, syncContext);
    return AsyncResultWrapper.Begin(newCallback, 
      state, beginDelegate, endDelegate, _processRequestTag);
  }
  else
  {
    // synchronous controller
    Action action = delegate
    {
      try
      {
        controller.Execute(RequestContext);
      }
      finally
      {
        factory.ReleaseController(controller);
      }
    };
 
    return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag);
  }
}
Как видно из приведённого выше кода, после создания экземпляра контроллера вызывается его метод BeginExecute, который класс Controller реализует явно, если контроллер асинхронный (по умолчанию так и есть, если вы не реализуете свой). Внутри вызов передаётся методу BeginExecuteCore
protected virtual IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
  PossiblyLoadTempData();
  try
  {
    string actionName = RouteData.GetRequiredString("action");
    IActionInvoker invoker = ActionInvoker;
    IAsyncActionInvoker asyncInvoker = invoker as IAsyncActionInvoker;
    if(asyncInvoker != null)
    {
      // asynchronous invocation
      BeginInvokeDelegate beginDelegate 
        = delegate(AsyncCallback asyncCallback, object asyncState)
      {
        return asyncInvoker.BeginInvokeAction(ControllerContext, 
          actionName, asyncCallback, asyncState);
      };
 
      EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
      {
        if(!asyncInvoker.EndInvokeAction(asyncResult))
        {
          HandleUnknownAction(actionName);
        }
      };
 
      return AsyncResultWrapper.Begin(callback, 
        state, beginDelegate, endDelegate, _executeCoreTag);
    }
    else
    {
      // synchronous invocation
      Action action = () =>
      {
        if(!invoker.InvokeAction(ControllerContext, actionName))
        {
          HandleUnknownAction(actionName);
        }
      };
      return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeCoreTag);
    }
  }
  catch
  {
    PossiblySaveTempData();
    throw;
  }
}
Данный метод использует свойство ActionInvoker (стандартно он содержит экземпляр типа AsyncControllerActionInvoker), для вызова метода действия. Имя метода действия берётся из данных маршрута в тестовом примере используется стандартное имя по умолчанию — Index. На самом деле всё самое интересное и важное происходит внутри кода этого класса, но вот приводить его я уже не стану, так как он слишком объёмный. Посмотреть его вы можете в исходном коде. Именно посредством данного класса происходит поиско нужного метода и его вызов, а так же поэтапное выполнение методов фильтров и приведённых в начале статьи методов интерфейсов. Для контроллера
namespace MvcApplication.Controllers
{
  public class HomeController : Controller
  {
    public ActionResult Index()
    {
      return View();
    }
  }
}
, который я использовал в целях демонстрации, вызывается метод Index, после метод –  View (хотя, как вы знаете контроллер может возвращать множество типов результатов). И цепочка вызовов методов начинает опускаться по стеку обратно, в конечном счёте, отправив результат клиенту . И так, завершился цикл статей про конвейер обработки запросов ASP.NET MVC 3 – 4. Была описана общая схема процесса обработки запросов, в некоторых местах достаточно детально и подробно. Надеюсь, что было интересно и полезно.