Главная страница   /   17.2. Создание пользовательской "фабрики" контроллеров (controller factory) (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

17.2. Создание пользовательской "фабрики" контроллеров (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
	{
		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 Предоставляет информацию о маршруте, который соответствует запросу

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

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

Цель метода 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. И, так как эти другие компоненты также могут быть заменены на пользовательский код (например, как представления в главе 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.

Листинг 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: Используем пользовательскую фабрику контроллеров