Главная страница   /   14.3. Настройка системы маршрутизации (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

14.3. Настройка системы маршрутизации

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

Создание пользовательской реализации RouteBase

Если вам не нравится способ, которым стандартные объекты Route соответствуют URL, или вы хотите реализовать что-то необычное, вы можете создать альтернативный класс из RouteBase. Это дает вам контроль над тем, как URL находят соответствие, как извлекаются параметры и как создаются исходящие URL.

Чтобы создать класс из RouteBase, вам нужно реализовать два метода:

  • GetRouteData(HttpContextBase httpContext): это механизм, при котором работает соответствие входящих URL. Фреймворк вызывает этот метод для каждой записи RouteTable.Routes, пока одна из них не вернет значение не-null.
  • GetVirtualPath(RequestContext requestContext, RouteValueDictionary values): Это механизм, при котором работает генерация исходящих URL. Фреймворк вызывает этот метод для каждой записи RouteTable.Routes, пока одна из них не вернет значение не-null.

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

Для начала нам нужно создать контроллер, который будет получать наследные запросы. Мы назвали наш контроллер LegacyController, и его содержимое показано в листинге 14-14.

Листинг 14-14: Класс LegacyController
using System.Web.Mvc;
namespace UrlsAndRoutes.Controllers
{
	public class LegacyController : Controller
	{
		public ActionResult GetLegacyURL(string legacyURL)
		{
			return View((object)legacyURL);
		}
	}
}

В этом простом контроллере метод действия GetLegacyURL принимает параметр и передает его в качестве модели представления в представление. Если бы мы действительно реализовали этот контроллер, мы бы использовали этот метод для извлечения файлов, которые были запрошены, а сейчас мы просто собираемся показать URL в представлении.

Совет

Заметьте, что мы передали параметр методу View в листинге 14-14. Одна из перегруженных версий метода View принимает строку, определяющую имя представления для отображения, и без добавления параметра это была бы перегруженная версия, которая заставляет компилятор С# думать, чего же мы хотим. Чтобы избежать этого, мы вызвали перегруженную версию, которая передает модель представления и использует представление по умолчанию. Мы также могли бы решить эту проблему, используя перегруженную версию, которая принимает и имя представления, и модель представления, но мы предпочитаем не создавать явных связей между методами действия и представлениями.

Представление, которое мы связали с этим методом действия, называется GetLegacyURL.cshtml. Оно показано в листинге 14-15.

Листинг 14-15: Представление GetLegacyURL
@model string
@{
	ViewBag.Title = "GetLegacyURL";
	Layout = null;
}
<h2>GetLegacyURL</h2>
The URL requested was: @Model

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

Роутинг входящих URL

Мы создали класс LegacyRoute, который мы поместили в папку Infrastructure (сюда мы помещаем классы поддержки, которые действительно не попадают в другие категории). Класс показан в листинге 14-16.

Листинг 14-16: Класс LegacyRoute
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes.Infrastructure
{
	public class LegacyRoute : RouteBase
	{
		private string[] urls;
		public LegacyRoute(params string[] targetUrls)
		{
			urls = targetUrls;
		}
		public override RouteData GetRouteData(HttpContextBase httpContext)
		{
			RouteData result = null;
			string requestedURL =
				httpContext.Request.AppRelativeCurrentExecutionFilePath;
			if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
			{
				result = new RouteData(this, new MvcRouteHandler());
				result.Values.Add("controller", "Legacy");
				result.Values.Add("action", "GetLegacyURL");
				result.Values.Add("legacyURL", requestedURL);
			}
			return result;
		}
		public override VirtualPathData GetVirtualPath(RequestContext requestContext,
		RouteValueDictionary values)
		{
			return null;
		}
	}
}

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

Если мы не можем обработать запрос, то мы просто возвращаем null, и система маршрутизации перейдет к следующему роуту в списке и повторит процесс. Если мы можем обработать запрос, нам необходимо вернуть экземпляр класса RouteData, содержащий значения для переменных controller и action, и все остальное, что мы хотим передать методу действия.

Когда мы создаем объект RouteData, мы должны передать в обработчик, с которым мы хотим работать, значения, которые мы генерируем. Мы собираемся использовать стандартный класс MvcRouteHandler, который и придает смысл значениям controller и action:

result = new RouteData(this, new MvcRouteHandler());

Для подавляющего большинства MVC приложений вам потребуется этот класс, так как он объединяет систему маршрутизации и модель контроллер/действие MVC приложения. Но вы можете заменить MvcRouteHandler, и мы покажем вам это в разделе «Создание пользовательского обработчика роутов» далее в этой главе.

В этой реализации мы готовы обработать любой запрос для URL, которые были переданы нашему конструктору. Когда мы получаем такой URL, мы добавляем жестко закодированные значения для контроллера и метода действия в объект RouteValues. Мы также добавляем свойство legacyURL. Заметьте, что имя этого свойства совпадает с именем параметра нашего метода действия, и это гарантирует, что значение, которое мы генерируем здесь, будет передано в метод действия с помощью параметра.

Последним шагом является регистрация нового роута, который использует наш вариант RouteBase. Вы можете увидеть, как это делается, в листинге 14-17, который показывает дополнение к файлу RouteConfig.cs.

Листинг 14-17: Регистрация пользовательской реализации RouteBase
public static void RegisterRoutes(RouteCollection routes) {
	routes.Add(new LegacyRoute(
		"~/articles/Windows_3.1_Overview.html",
		"~/old/.NET_1.0_Class_Library"));
	routes.MapRoute("MyRoute", "{controller}/{action}");
	routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}

Мы создаем новый экземпляр нашего класса LegacyRoute и передаем ему нужные URL. Затем мы добавляем объект в RouteCollection с помощью метода Add. Теперь, когда мы запустим приложение и запросим один из URL, которые мы определили, запрос будет обрабатываться нашим пользовательским классом и будет нацелен на наш контроллер, как показано на рисунке 14-4.

Рисунок 14-4: Роутинг запросов при помощи пользовательской реализации RouteBase

Генерирование исходящих URL

Для поддержки создания исходящих URL мы должны реализовать метод GetVirtualPath в нашем классе LegacyRoute. Еще раз, если мы не в состоянии справиться с запросом, мы даем знать об этом системе маршрутизации, возвращая null. В противном случае мы возвращаем экземпляр класса VirtualPathData. Листинг 14-18 показывает нашу реализацию этого метода.

Листинг 14-18: Реализация метода GetVirtualPath
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
{
	VirtualPathData result = null;
	if (values.ContainsKey("legacyURL") &&
		urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase)) 
	{
		result = new VirtualPathData(this, new UrlHelper(requestContext)
			.Content((string)values["legacyURL"]).Substring(1));
	}
	return result;
}

Мы передали сегментные переменные и другую сопутствующую информацию, используя анонимные типы, а система маршрутизации преобразовала их в объекты RouteValueDictionary. Листинг 14-19 показывает дополнение к ActionName.cshtml, который генерирует исходящий URL с помощью нашего пользовательского роута.

Листинг 14-19: Создание исходящего URL при помощи пользовательского роута
@{
	Layout = null;
}
<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>ActionName</title>
</head>
<body>
	<div>The controller is: @ViewBag.Controller</div>
	<div>The action is: @ViewBag.Action</div>
	<div>
		This is a URL:
		@Html.ActionLink("Click me", "GetLegacyURL",
			new { legacyURL = "~/articles/Windows_3.1_Overview.html" })
	</div>
</body>
</html>

Когда это представление рендерится, вспомогательный метод ActionLink генерирует следующий HTML, как вы и могли ожидать:

<a href="/articles/Windows_3.1_Overview.html">Click me</a>

Анонимный тип, созданный при помощи legacyURL, преобразуется в класс RouteValueDictionary, который содержит ключ с тем же именем. В этом примере мы решаем, что мы можем работать с запросом для исходящего URL, если есть ключ с именем legacyURL и если его значение является одним из URL, переданным в конструктор. Мы могли бы это еще конкретизировать и проверить значения controller и action, но для простого примера этого достаточно.

Если мы получаем соответствие, мы создаем новый экземпляр VirtualPathData, передавая ему ссылку на текущий объект и исходящий URL. Мы использовали метод Content класса UrlHelper для преобразования относительного URL в тот, который может быть передан в браузер. Система маршрутизации добавляет дополнительный / в URL, поэтому мы должны позаботиться о том, чтобы удалить символ из нашего генерируемого URL.

Создание пользовательского обработчика роутов

Мы работали с MvcRouteHandler в наших примерах, потому что он соединяет систему маршрутизации к MVC фреймворком. И поскольку наше внимание сосредоточено MVC, это нам фактически всегда подходит. Несмотря на это, система маршрутизации позволяет нам определять наш собственный обработчик роутов путем реализации интерфейса IRouteHandler. Листинг 14-20 показывает содержимое класса CustomRouteHandler, который мы добавили в папку Infrastructure нашего проекта.

Листинг 14-20: Реализация интерфейса IRouteHandler
using System.Web;
using System.Web.Routing;
namespace UrlsAndRoutes.Infrastructure
{
	public class CustomRouteHandler : IRouteHandler
	{
		public IHttpHandler GetHttpHandler(RequestContext requestContext)
		{
			return new CustomHttpHandler();
		}
	}
	public class CustomHttpHandler : IHttpHandler
	{
		public bool IsReusable
		{
			get { return false; }
		}
		public void ProcessRequest(HttpContext context)
		{
			context.Response.Write("Hello");
		}
	}
}

Цель интерфейса IRouteHandler заключается в обеспечении средства для генерации реализаций интерфейса IHttpHandler, который отвечает за обработку запросов. При MVC реализации этих интерфейсов находятся контроллеры, вызываются методы действия, рендерятся представления и результаты записываются в ответ. Наша реализация немного проще. Она просто пишет клиенту слово Hello (не HTML документ, содержащий это слово, а только текст). Мы можем зарегистрировать наш пользовательский обработчик в файле RouteConfig.cs, когда мы определяем роут, как показано в листинге 14-21.

Листинг 14-21: Использование пользовательского обработчика роутов
public static void RegisterRoutes(RouteCollection routes) {
	routes.Add(new Route("SayHello", new CustomRouteHandler()));
	routes.Add(new LegacyRoute(
		"~/articles/Windows_3.1_Overview.html",
		"~/old/.NET_1.0_Class_Library"));
	routes.MapRoute("MyRoute", "{controller}/{action}");
	routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}

Когда мы запрашиваем URL /SayHello, наш обработчик используется для обработки запроса. На рисунке 14-5 показан результат.

Рисунок 14-5: Использование пользовательского обработчика роутов

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