ASP.NET MVC 4
Адам Фриман
Настройка системы маршрутизации
Вы видели, какой гибкой и настраиваемой является система маршрутизации, но если она не соответствует вашим требованиям, вы можете подстроить ее поведение под ваши нужды. В этом разделе мы покажем вам два способа, как сделать это.
Создание пользовательской реализации 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 фреймворка и игнорировать другие или даже реализовать совершенно новый архитектурный паттерн.