Pull to refresh

Жизненный цикл Request в MVC 3 Framework приложении

Reading time7 min
Views8.5K
Любой разработчик знает, что URL в маршрутизации MVC играет ключевую роль. Как известно, существует стандартная маршрутизация:

routes.MapRoute(
                "Default",
                "{controller}/{action}/{id}",
                new { controller = "Home", action = "Index", id = UrlParameter.Optional );


Она представляет собой правило по которым выбираются маршруты. В простейших случаях, можно поменять местами action и controller, или добавить еще множество параметров, которые будут выглядеть в url следующим образом- param1/param2/param3/. Иногда, например в CMS системах, необходимо сделать так, чтоб часть url приходила как переменная.

routes.MapRoute(
                "Default",
                "{controller}/{action}/{*url}",
               new { controller = "Content", action = "Home", url = UrlParameter.Optional
});


Это правило выделяет Action и Controller, а остальную часть присваивает переменной url.
Регистрируются Routes при старте приложения. А именно в файле Global.asax в методе Application_Start().

Для создания большинства приложений этих знаний хватает. Цель же моей статьи пройти весь жизненный цикл Request и посмотреть, что же происходит, перед тем как приложение разберет url и передаст управление нужному action.

1. Создание обработчика запроса MVC.
MVC включает следующие типы обработчика: (По материалам MSDN)
• MvcHandler. Этот обработчик отвечает за инициирование конвейера ASP.NET для приложения MVC. Он получает экземпляр Controller из фабрики MVC контроллера; данный контроллер выполняет дальнейшую обработку запроса. Обратите внимание, что несмотря на то, что MvcHandler реализует IHttpHandler, его нельзя сопоставить как обработчик (например, для расширения имени файла .mvc), поскольку класс не поддерживает конструктор без параметров. (Единственный конструктор требуется объект RequestContext).
• MvcRouteHandler. Этот класс реализует IRouteHandler, поэтому его можно интегрировать с маршрутизацией ASP.NET. Класс MvcRouteHandler связывает маршрут с экземпляром MvcHandler. Экземпляр MvcRouteHandler регистрируется с маршрутизацией, при использовании метода MapRoute. При вызове класса MvcRouteHandler класс создает экземпляр MvcHandler, используя текущий экземпляр RequestContext. Затем он передает элемент управления новому экземпляру MvcHandler.
• MvcHttpHandler. Этот обработчик используется для упрощения прямого сопоставления обработчика минуя модуль маршрутизации. Это полезно, при необходимости сопоставления расширения имени файла, например .mvc, непосредственно обработчику MVC. На внутреннем уровне, MvcHttpHandler выполняет одни и те же задачи, что и обычная маршрутизация ASP.NET (проходя через MvcRouteHandler и MvcHandler). Однако, он выполняет эти задачи как обработчик, а не модуль. Этот обработчик обычно не используется, если UrlRoutingModule включен для всех запросов.


Для чего это может понадобиться?
  • a. Простейший пример для локализации приложений (Thread.CurrentThread.CurrentUICulture и Thread.CurrentThread.CurrentCulture).
  • b. Для маршрутизации между несколькими приложениями.
  • c. Для выполнения настроек перед Dependency Injection (например выбор строки подключения к DB).

Общий вид переопределения MvcRouteHandler следующий:

public class CustomRouteHandler : MvcRouteHandler
    {
        protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            //TODO: something
            return base.GetHttpHandler(requestContext);
        }
    }


Инициализировать CustomRouteHandler нужно в классе Global методе Application_Start()

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            var route = routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
            route.RouteHandler = new CustomRouteHandler();


2. Выбор и создание контроллера
За выбор и создание контроллера отвечает класс ControllrerFactory. Этот класс реализует интерфейс IControllerFactory

    public interface  IControllerFactory
    {
        IController CreateController(RequestContext requestContext, string controllerName);
        SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
        void ReleaseController(IController controller);
    }


Естественно вы можете создать свою фабрику контроллеров, реализуя этот интерфейс, но как правило, для удовлетворения потребностей достаточно переопределить методы класса DefaultControllerFactory. Как правило это IController CreateController(RequestContext requestContext, string controllerName). Ниже пример переопределенной фабрики контроллеров.

 public class MyControllerFactory : DefaultControllerFactory
    {
        public override IController CreateController(RequestContext requestContext, string controllerName)
        {
           //TODO: something
            Type controllerType = this.GetControllerType(requestContext, controllerName);
            return this.GetControllerInstance(requestContext, controllerType);
        }
 
        public override void ReleaseController(IController controller)
        {
            //TODO: something
            var disposable = controller as IDisposable;
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }
 
        protected override IController GetControllerInstance(RequestContext requestContext,
                                                             Type controllerType)
        {
            //TODO: something
            try
            {
                return (IController)Activator.CreateInstance(controllerType);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException();
            }
        }
   }


Между прочем Autofac, Ninject, Unity все они создают свою фабрику контроллеров для Dependency Injection.
Регистрирование собственной фабрики, так же происходит в классе Global, методе Application_Start()
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());

3. Action
После создания нужно экземпляра контроллера вызывается метод IController.Execute(). В методе Execute() мы должны выполнить необходимые нам действия. В стандартном классе Controller управление передается ActionInvoker, он то и отвечает за вызов определенного action.

public class CustomActionInvoker : IActionInvoker
    {
        public bool InvokeAction(ControllerContext context, string actionName)
        {
            //TODO: invoke Action
            // if action was found
            // return true
           return false;
        }
    }


Зарегистрировать в контроллере его можно инициализировав в конструкторе.
public class CustomActionInvokerController : Controller
    {
        public CustomActionInvokerController()
        {
            this.ActionInvoker = new CustomActionInvoker();
        }
    }


ActionInvoker также перед вызовом Action проверяет его атрибуты, производные от ActionMethodSelectorAttribute и FilterAttribute (AuthorizeAttribute, HandleErrorAttibute, ValidateAntiForgeryTokenAttribute, ValidateInputAttribute). Эти атрибуты позволяют принять решение, можно ли передать управление тому или иному action.
Для создания своих action selectorов – наследование от этого атрибута

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public abstract class ActionMethodSelectorAttribute : Attribute {
        public abstract bool IsValidForRequest(ControllerContext controllerContext, 
            MethodInfo methodInfo);
    }


4. Binders
Как мы уже знаем, механизм MVC позволяет помимо простейших параметров(int, string), использовать более сложные, например классы. Но иногда нужно принять, достаточно экзотический класс, с которым стандартный binder не справляется. В этом случае мы можем написать свой собственный binder. Для этого достаточно наследоваться от DefaultModelBinder и переопределить его методы.

public class CustomModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //TODO: something
            return base.BindModel(controllerContext, bindingContext);
        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            //TODO: something
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }

        protected override System.ComponentModel.PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //TODO: something
            return base.GetModelProperties(controllerContext, bindingContext);
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            //TODO: something
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }

        protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        }

        protected override ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //TODO: something
            return base.GetTypeDescriptor(controllerContext, bindingContext);
        }

        protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            base.OnModelUpdated(controllerContext, bindingContext);
        }

        protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //TODO: something
            return base.OnModelUpdating(controllerContext, bindingContext);
        }

        protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value);
        }

        protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            //TODO: something
            return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value);
        }

        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
        {
            //TODO: something
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
        }
    }


Как видим базовый класс нам предоставляет массу возможностей.
Подключать свой ModelBinder можно непосредственно к модели. При помощи атрибута.

[ModelBinder(typeof(CustomModelBinder))]


Либо в классе Global, методе Application_Start().

ModelBinders.Binders.Add(typeof(CustomTypeModel), new CustomModelBinder());


5 ActionResult
И наконец после того как отработал action, on возвращает ActionResult(). Это может быть View(), PartialView(), JsonResult(), FileResult(), EmptyResult().
Естественно этот список может быть расширен и дополнен, нашими реализациями ActionResult(). Для этого достаточно, наследоваться от класса ActionResult, и переопределить метод void ExecuteResult(ControllerContext context)

public class CustomResult : ActionResult
{
  public override void ExecuteResult(ControllerContext context)
  {
     //TODO: something
  }
}


Так же есть интересный способ добавить нужные методы на View() и пользоваться ими непосредственно из *.cshtml файла. Для этого наследуемся от класса WebViewPage.

public abstract class CustomView : WebViewPage
    {
         public string HelloWordPrint()
	{
              return "Hello Word";
	}
    }


В самой же RazorView мы используем следующую директиву

@inherits CustomView


Теперь вы можете использовать новые методы во View.

Заключение
Вот и все. В этом посте мы прошли все этапы Request в MVC, мы рассмотрели как мы можем влиять на каждый этап. Надеюсь теперь у вас сложилась полная картина, о том как работает MVC3 framework.
Tags:
Hubs:
Total votes 13: ↑8 and ↓5+3
Comments14

Articles