Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Расширенные возможности контроллера

В этой главе мы покажем вам некоторые дополнительные возможности MVC для работы с контроллерами. Для начала рассмотрим элементы конвейера обработки запросов, которые запускают выполнение метода действия, и продемонстрируем различные способы управления этим процессом. На рисунке 17-1 показан базовый поток передачи управления между компонентами.

Рисунок 17-1: Вызов метода действия

В первой части этой главы в центре нашего внимания будет фабрика контроллеров и средство вызова действий. Назначение этих компонентов можно понять по их названиям. Фабрика контроллеров создает экземпляры контроллеров для обслуживания запроса, а средство вызова действий отвечает за поиск и вызов методов действий в классе контроллера. MVC Framework включает стандартные реализации обоих компонентов, и мы рассмотрим, как их конфигурировать и контролировать их поведение. Мы также покажем, как заменять эти компоненты и использовать пользовательскую логику.

Создание проекта для примера

Для этой главы мы создадим новый проект MVC под названием ControllerExtensibility на шаблоне Empty. Для работы нам потребуются несколько простых контроллеров, чтобы продемонстрировать различные возможности расширения. Для начала определим класс Result.cs в папке Models, содержимое которого показано в листинге 17-1.

Листинг 17-1: Объект модели Result

namespace ControllerExtensibility.Models

{

public class Result {

public string ControllerName { get; set; } public string ActionName { get; set; }

}

}

Далее мы создадим папку /Views/Shared и добавим новое представление под названием Result.cshtml. Это представление, которое будут визуализировать все методы действий в наших классах контроллерах, его содержимое показано в листинге 17-2.

Листинг 17-2: Содержимое файла Result.cshtml

@model ControllerExtensibility.Models.Result

@{

Layout = null;

}

<!DOCTYPE html> <html>

<head>

421

<meta name="viewport" content="width=device-width" /> <title>Result</title>

</head>

<body>

<div>Controller: @Model.ControllerName</div> <div>Action: @Model.ActionName</div>

</body>

</html>

Это представление использует класс Result, который мы определили в листинге 17-1, в качестве модели и просто отображает значения свойств ControllerName и ActionName. Наконец, нам нужно создать несколько базовых контроллеров.

В листинге 17-3 показан контроллер Product.

Листинг 17-3: Контроллер Product

using ControllerExtensibility.Models; using System.Web.Mvc;

namespace ControllerExtensibility.Controllers

{

public class ProductController : Controller

{

public ViewResult Index()

{

return View("Result", new Result

{

ControllerName = "Product", ActionName = "Index"

});

}

public ViewResult List()

{

return View("Result", new Result

{

ControllerName = "Product", ActionName = "List"

});

}

}

}

В листинге 17-4 показан контроллер Customer.

Листинг 17-4: Контроллер Customer

using ControllerExtensibility.Models; using System.Web.Mvc;

namespace ControllerExtensibility.Controllers

{

public class CustomerController : Controller

{

public ViewResult Index()

{

return View("Result", new Result

{

ControllerName = "Customer", ActionName = "Index"

});

}

422

public ViewResult List()

{

return View("Result", new Result

{

ControllerName = "Customer", ActionName = "List"

});

}

}

}

Наши контроллеры сообщают представлению Result.cshtml, что было вызвано, – больше ничего полезного они не делают. В этой главе нам больше ничего и не нужно, так как здесь мы учимся изменять способы обработки контроллеров и действий.

Создание пользовательской "фабрики"

контроллеров (controller factory)

Как и для большинства других элементов MVC Framework, лучший способ понять принцип работы фабрики контроллеров – это создать ее пользовательскую реализацию. Мы не рекомендуем вам делать это в реальных проектах, так как гораздо проще создать пользовательское поведение, расширяя встроенную фабрику. Таким образом, мы хотим только продемонстрировать, как MVC Framework создает экземпляры контроллеров. Фабрики контроллеров определяются интерфейсом IControllerFactory, который показан в листинге 17-5.

Листинг 17-5: Интерфейс IControllerFactory

using System.Web.Routing;

using System.Web.SessionState; namespace System.Web.Mvc

{

public interface IControllerFactory

{

IController CreateController(RequestContext requestContext, string controllerName);

SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);

void ReleaseController(IController controller);

}

}

В следующих разделах мы создадим простую пользовательскую фабрику контроллеров и продемонстрируем реализации для каждого метода в интерфейсе IControllerFactory. Для этого добавим папку Infrastructure и в ней определим класс CustomControllerFactory.cs.

Пользовательская фабрика контроллеров показана в листинге 17-6.

Листинг 17-6: Содержимое файла CustomControllerFactory.cs

using System;

using System.Web.Mvc; using System.Web.Routing;

using System.Web.SessionState;

using ControllerExtensibility.Controllers;

namespace ControllerExtensibility.Infrastructure

{

public class CustomControllerFactory : IControllerFactory

423

{

public IController CreateController(RequestContext requestContext, string controllerName)

{

Type targetType = null; switch (controllerName)

{

case "Product":

targetType = typeof(ProductController); break;

case "Customer":

targetType = typeof(CustomerController); break;

default:

requestContext.RouteData.Values["controller"] = "Product"; targetType = typeof(ProductController);

break;

}

return targetType == null ? null : (IController)DependencyResolver.Current.GetService(targetType);

}

public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext,

string controllerName)

{

return SessionStateBehavior.Default;

}

public void ReleaseController(IController controller)

{

IDisposable disposable = controller as IDisposable; if (disposable != null)

{

disposable.Dispose();

}

}

}

}

Самый важный метод в интерфейсе - это CreateController, который вызывается, когда платформе требуется контроллер для обслуживания запроса. Параметрами этого метода являются объект RequestContext, который позволяет фабрике просматривать информацию о запросе, и string, который содержит имя контроллера, полученное из URL. Класс RequestContext определяет свойства, описанные в таблице 17-1.

Таблица 17-1: Свойства RequestContext

Название

 

Тип

Описание

 

HttpContext

 

HttpContextBase

Предоставляет информацию о HTTP-запросе

 

 

 

 

RouteData

 

RouteData

Предоставляет информацию о маршруте, который соответствует

 

запросу

 

 

 

Мы не рекомендуем создавать пользовательский контроллер таким образом в том числе и потому, что поиск классов контроллеров в приложении и создание их экземпляров – сложный процесс, вы должны быть в состоянии динамически и последовательно размещать контроллеры, а также устранить все виды потенциальных проблем, такие как различение классов с одинаковыми именами в разных пространствах имен, исключения конструкторов и многие другие.

424

В нашем примере проекта только два контроллера, и для создания их экземпляров мы пропишем имена соответствующих классов в фабрике контроллеров – определенно, это не самая лучшая идея для реального проекта.

Цель метода CreateController - создание экземпляров классов контроллеров, которые могут обработать текущий запрос. Для него нет никаких ограничений, только одно единственное правило, согласно которому результатом метода должен быть объект, который реализует интерфейс

IController.

Соглашения, которые вы уже встречали ранее в этой книге, существуют для того, чтобы пользовательская фабрика контроллеров была написана по образцу стандартной. К примеру, мы реализовали одно из таких соглашений в коде: когда мы получаем запрос к контроллеру, мы добавляем к его имени Controller, так что запрос к Product приводит к созданию экземпляра класса

ProductController.

Создавая фабрику контроллеров, вы можете следовать соглашениям MVC или отказаться от них и создать свои собственные, которые больше подходят вашему проекту. Мы не думаем, что имеет смысл создавать новые соглашения только ради создания новых соглашений, но это может проиллюстрировать гибкость платформы MVC.

Резервный контроллер

Пользовательская фабрика контроллеров должна возвращать реализацию интерфейса IController как результат метода CreateController, иначе будет отображена ошибка. Это означает, что нужно иметь запасной вариант для запросов, которым не будет соответствовать ни один контроллер в вашем проекте. Эту проблему можно решать по-разному: вы можете определить специальный контроллер, который будет отображать сообщение об ошибке, или, что и сделали мы, соотнести запрос с каким-нибудь другим классом контроллера.

Когда мы получаем запрос, который не соответствует ни одному контроллеру в нашем проекте, мы соотносим его с классом ProductController. Это может быть не самым полезным решением в реальном проекте, но здесь оно демонстрирует, что фабрика контроллеров обладает полной гибкостью в интерпретации запросов. Однако здесь вам необходимо понимать, как работают другие компоненты MVC Framework.

По умолчанию MVC выбирает представление на основе значения controller в данных маршрутизации, а не имени класса контроллера. Итак, если мы хотим, чтобы запасной контроллер в нашем примере работал с представлениями, которые организованы по имени контроллера, нам необходимо изменить свойство маршрутизации controller:

requestContext.RouteData.Values["controller"] = "Product";

Это изменение приведет к тому, MVC Framework будет искать представления, связанные с нашим резервным контроллером, а не тем, который был запрошен пользователем.

Здесь есть два важных момента. Во-первых, фабрика контроллеров не только соотносит запросы с контроллерами, но и может изменять запросы, чтобы повлиять на поведение на определенных этапах в конвейере обработки запроса. У этой ее возможности большой потенциал, и она является важной характеристикой MVC Framework.

Во-вторых, даже если вы можете создавать любые соглашения для фабрики контроллеров, не забывайте, что соглашения соблюдаются и в других компонентах MVC Framework. И, так как эти другие компоненты также могут быть заменены на пользовательский код (например, как

425

представления в главе 18), то имеет смысл следовать соглашениям везде, где это возможно, благодаря чему компоненты можно будет разрабатывать и использовать независимо друг от друга.

Создаем экземпляры классов контроллеров

Создание экземпляров классов контроллеров не регулируется никакими правилами, но удобнее всего использовать для этого преобразователь зависимостей (DR), который мы рассматривали в главе 6. Он позволит сохранить главной задачей пользовательской фабрики сопоставление запросов с классами контроллеров, а такие вопросы, как внедрение зависимостей, будут обрабатываться отдельно и для всего приложения. Для создания экземпляров наших контроллеров мы использовали класс DependencyResolver:

return targetType == null ? null : (IController)DependencyResolver.Current.GetService(targetType);

Статическое свойство DependencyResolver.Current возвращает реализацию интерфейса

IDependencyResolver, который определяет метод GetService. Вы передаете объект System.Type в

этот метод, а он возвращает экземпляр этого объекта. Существует строго типизированный вариант метода GetService, но так как заранее мы не знаем, с каким типом будем работать, то придется использовать вариант, который возвращает Object, а затем явно передаете его в IController.

Подсказка

Мы не проверяли, является ли объект, который возвращает метод GetService, реализацией IController, но в реальных проектах это нужно делать, особенно если

вы используете классы, созданные другими разработчиками.

Реализуем другие методы интерфейса

Винтерфейсе IControllerFactory осталось два метода:

Метод GetControllerSessionBehavior используется MVC Framework, чтобы определить, нужно ли предоставлять контроллеру данные сессии. К нему мы вернемся в разделе "Используем контроллеры без поддержки состояния сессии" далее в этой главе.

Метод ReleaseController вызывается, когда объект контроллера, созданный методом CreateController, больше не нужен. В нашей реализации мы проверяем, реализует ли класс интерфейс IDisposable. Если реализует, мы вызываем метод Dispose, чтобы освободить все ресурсы, которые возможно.

Наша реализация методов GetControllerSessionBehaviour и ReleaseController подходит для большинства проектов и ее можно использовать без изменений (хотя сначала прочитайте раздел о контроллерах без поддержки состояния сессии далее в этой главе, чтобы убедиться, что вы понимаете все доступные варианты).

Регистрируем пользовательскую фабрику контроллеров

Чтобы сообщить MVC Framework, что он должен использовать пользовательскую фабрику контроллеров, мы используем класс ControllerBuilder. Зарегистрировать пользовательскую фабрику нужно при запуске приложения, то есть с помощью метода Application_Start в файле Global.asax.cs, как показано в листинге 17-7.

426

Листинг 17-7: Регистрируем пользовательскую фабрику контроллеров

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Http; using System.Web.Mvc;

using System.Web.Routing;

using ControllerExtensibility.Infrastructure;

namespace ControllerExtensibility

{

public class MvcApplication : System.Web.HttpApplication

{

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());

}

}

}

Когда фабрика зарегистрирована, она будет обрабатывать все запросы, которые поступают в приложение. Вы можете увидеть эффект применения пользовательской фабрики, просто запустив приложение - браузер запросит корневой URL, который будет отображен системой маршрутизации в контроллер Home. Наша пользовательская фабрика обработает запрос для контроллера Home, создав экземпляра класса ProductController, что приведет к результату, показанному на рисунке 17-2.

Рисунок 17-2: Используем пользовательскую фабрику контроллеров

Работа со встроенной фабрикой контроллеров

Мы создали пользовательскую фабрику контроллеров, потому что это наиболее эффективный способ показать, какими обязанностями обладает фабрика контроллеров и как она функционирует. Однако в большинстве случаев встроенный класс фабрики контроллеров, DefaultControllerFactory, будет нас вполне удовлетворять. Получая запрос от системы маршрутизации, эта фабрика просматривает данные маршрутизации, чтобы найти значение свойства controller (которое мы изучили в главе 13), и пытается найти в приложении класс, который отвечает следующим критериям:

класс должен быть помечен как public;

класс должен быть конкретным (не помечен как abstract);

427

класс не должен принимать общие (generic) параметры;

имя класса должно заканчиваться на Controller;

класс должен реализовывать интерфейс IController.

Класс DefaultControllerFactory поддерживает список таких классов в приложении, так что ему не нужно выполнять поиск каждый раз, когда поступает запрос. Если подходящий класс существует, то он создает экземпляр с помощью активатора контроллера (мы вернемся к этому в следующем разделе "Настраиваем процедуру создания экземпляров контроллеров в DefaultControllerFactory"), и на этом его работа завершена. Если подходящего контроллера нет, то запрос не может быть обработан далее.

Обратите внимание на то, что для класса DefaultControllerFactory соглашение имеет высший приоритет, чем конфигурация (convention over configuration pattern). Вам не нужно регистрировать контроллеры в файле конфигурации, потому что их найдет для вас фабрика. Все, что от вас потребуется - это создать классы, отвечающие критериям, по которым их ищет фабрика.

Если вы хотите создать пользовательское поведение фабрики контроллеров, можно изменить настройки стандартной фабрики или переопределить некоторые методы. Таким образом, вы сможете использовать поведение с высшим приоритетом соглашений, не создавая их заново и таким образом избегая наиболее сложной работы. В следующих разделах мы продемонстрируем вам различные способы изменения процедуры создания контроллеров.

Назначаем приоритет пространствам имен

В главе 14 мы показали вам, как назначить приоритет одному или нескольким пространствам имен при создании маршрута. Это решало проблему неоднозначности контроллеров, когда классы контроллеров имели одинаковые имена, но располагались в разных пространствах имен. За обработку списка пространств имен и назначение им приоритетов отвечает

DefaultControllerFactory.

Подсказка

Глобальные приоритеты переопределяются конкретными приоритетами, назначенными для маршрутов. Это означает, что вы можете определить глобальные правила, а затем изменить конкретные маршруты по мере необходимости. Подробнее о пространствах имен для конкретных маршрутов можно узнать в главе 14.

Если у вас в приложении много маршрутов, то более удобно задавать приоритетные пространства имен глобально, чтобы они применялись ко всем маршрутам. В листинге 17-8 показано, как сделать это в методе Application_Start файла Global.asax (это наш выбор, но вы также можете использовать файл RouteConfig.cs в папке App_Start).

Листинг 17-8: Назначаем глобальные приоритеты пространствам имен

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Http; using System.Web.Mvc;

using System.Web.Routing;

using ControllerExtensibility.Infrastructure;

428

namespace ControllerExtensibility

{

public class MvcApplication : System.Web.HttpApplication

{

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

RouteConfig.RegisterRoutes(RouteTable.Routes);

ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace"); ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*");

}

}

}

Мы используем статический метод ControllerBuilder.Current.DefaultNamespaces.Add, чтобы добавить пространства имен, которым хотим назначить приоритет. Порядок, в котором мы добавляем пространства имен, не влияет на порядок поиска и не предполагает относительный приоритет относительно друг друга - все пространства имен, определенные методом Add, обрабатываются одинаково, а их приоритет является относительным только по отношению к тем пространствам, которые не были добавлены методом Add. Это означает, что если фабрика не найдет подходящий класс контроллера в пространствах имен, определенных методом Add, то она произведет поиск по всему приложению.

Подсказка

Обратите внимание, что во втором операторе, выделенным жирным шрифтом в листинге 17-8, используется символ звездочки (*). Таким образом мы указываем, что фабрика контроллеров должна производить поиск в пространстве имен MyProject и во всех дочерних пространствах имен, которые содержит MyProject. Хотя * и

напоминает синтаксис регулярных выражений, но не является им; вы можете закончить пространства имен символом *, но не сможете использовать никакие другие элементы синтаксиса регулярных выражений в методе Add.

Настраиваем процедуру создания экземпляров контроллеров в

DefaultControllerFactory

Существует несколько способов изменить то, как класс DefaultControllerFactory создает экземпляры объектов контроллеров. Сейчас фабрику контроллеров наиболее часто изменяют для того, чтобы добавить поддержку DI. Это можно сделать с помощью разных приемов. Выбор наиболее подходящего будет зависить от того, как вы используете DI в других компонентах приложения.

Используем DR

Класс DefaultControllerFactory будет использовать доступный DR для создания контроллеров. Мы рассмотрели DR в главе 6 и продемонстрировали вам класс NinjectDependencyResolver, который реализует интерфейс IDependencyResolver для поддержки Ninject DI. Мы также использовали класс DependencyResolver ранее в этой главе, когда создавали пользовательскую фабрику контроллеров.

429

DefaultControllerFactory вызовет метод IDependencyResolver.GetService, чтобы запросить экземпляр контроллера, который дает возможность преобразовать и внедрить любую зависимость.

Используем активатор контроллеров

Вы также можете использовать в контроллерах DI, создав активатор контроллеров (controller activator). Он создается реализацией интерфейса IControllerActivator, который показан в листинге

17-9.

Листинг 17-9: Интерфейс IControllerActivator

namespace System.Web.Mvc

{

using System.Web.Routing;

public interface IControllerActivator

{

IController Create(RequestContext requestContext, Type controllerType);

}

}

Интерфейс содержит один метод, Create, в который передается объект RequestContext, описывающий запрос, и Type, который определяет, для какого класса должен быть создан экземпляр. Реализация этого интерфейса показана в листинге 17-10.

Листинг 17-10: Реализация интерфейса IControllorActivator

using ControllerExtensibility.Controllers; using System;

using System.Web.Mvc; using System.Web.Routing;

namespace ControllerExtensibility.Infrastructure

{

public class CustomControllerActivator : IControllerActivator

{

public IController Create(RequestContext requestContext, Type controllerType)

{

if (controllerType == typeof(ProductController))

{

controllerType = typeof(CustomerController);

}

return (IController)DependencyResolver.Current.GetService(controllerType);

}

}

}

Реализация IControllerActivator довольно проста - если запрашивается класс ProductController, она отвечает экземпляром класса CustomerController. Для реального проекта это не лучшее решение, но здесь мы только показываем, как можно использовать интерфейс IControllerActivator для перехвата запросов между фабрикой контроллеров и DR.

Чтобы использовать пользовательский активатор, нам нужно передать экземпляр реализованного класса в конструктор DefaultControllerFactory и зарегистрировать результат в методе

Application_Start файла Global.asax, как показано в листинге 17-11.

Листинг 17-11: Регистрируем пользовательский активатор

using System;

using System.Collections.Generic; using System.Linq;

using System.Web;

430

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]