Главная страница   /   17.3. Работа со встроенной фабрикой контроллеров (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

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

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

  • класс должен быть помечен как public;
  • класс должен быть конкретным (не помечен как abstract);
  • класс не должен принимать общие (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;

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 ранее в этой главе, когда создавали пользовательскую фабрику контроллеров.

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;
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
				DefaultControllerFactory(new CustomControllerActivator()));
		}
	}
}

Вы можете увидеть эффект применения пользовательского активатора, если запустите приложение и перейдите по ссылке /Product. Маршрут указывает на контроллер Product, и DefaultControllerFactory попросит активатор создать экземпляр класса ProductFactory - но вместо этого активатор перехватывает запрос и создает экземпляр класса CustomerController. Результат показан на рисунке 17-3.

Рисунок 17-3: Перехват запросов на создание экземпляра с помощью пользовательского активатора контроллера

Переопределяем методы DefaultControllerFactory

Чтобы изменить процедуру создания контроллеров, можно переопределить методы в классе DefaultControllerFactory. В таблице 17-2 описаны три переопределяемых метода, каждый из которых выполняет несколько специфическую задачу.

Таблица 17-2: Переопределяемые методы DefaultContollerFactory
Метод Результат Описание
CreateController IController Реализация метода CreateController из интерфейса IControllerFactory. По умолчанию этот метод вызывает GetControllerType, чтобы определить, для какого типа нужно создать экземпляр, а затем получает объект контроллера, передавая результат в метод GetControllerInstance.
GetControllerType Type Соотносит запросы с типами контроллеров. Здесь применяется большинство критериев, перечисленных ранее в этой главе.
GetControllerInstance IController Создает экземпляр указанного типа.