Главная страница   /   13.7. Ограничение роутов (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

13.7. Ограничение роутов

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

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

Ограничение роута при помощи регулярного выражения

Первый способ, который мы рассмотрим, заключается в ограничении роута при помощи регулярных выражений. Листинг 13-27 содержит пример.

Листинг 13-27: Использование регулярного выражения для ограничения роута
...
public static void RegisterRoutes(RouteCollection routes) {
	routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
		new { controller = "Home", action = "Index", id = UrlParameter.Optional },
		new { controller = "^H.*"},
		new[] { "URLsAndRoutes.Controllers"});
}
...

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

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

Примечание

Значения по умолчанию используются до проверки на ограничения. Так, например, если мы запрашиваем URL /, для controller применяется значение по умолчанию Home. Затем проверяются ограничения, и поскольку значение controller начинается с H, то URL по умолчанию будет соответствовать роуту.

Ограничение роута до набора указанных значений

Мы можем использовать регулярные выражения, чтобы ограничить роут таким образом, что только конкретные значения URL сегмента будут способствовать соответствию URL и роута. Это можно сделать, используя символ вертикальной черты (|), как показано в листинге 13-28.

Листинг 13-28: Ограничение роута до определенного набора значений сегментных переменных
...
public static void RegisterRoutes(RouteCollection routes) {
	routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
		new { controller = "Home", action = "Index", id = UrlParameter.Optional },
		new { controller = "^H.*", action = "^Index$|^About$"},
		new[] { "URLsAndRoutes.Controllers"});
}
...

Это ограничение позволяет роуту соответствовать только тем URL, где значениями сегмента действия являются Index или About. Ограничения применяются вместе, так что ограничения, накладываемые на значение переменной action, сочетаются со значениями, накладываемыми на переменную controller. Это означает, что роут в листинге 13-28 будет только в том случае соответствовать URL, если переменная controller начинается с буквы H, а для переменной action есть значения Index или About. Итак, теперь вы видите, что мы понимаем под созданием идеально точных роутов.

Ограничение роутов при помощи HTTP методов

Мы можем ограничить роуты, чтобы они соответствовали URL только тогда, когда он запрашивается с помощью конкретного HTTP метода, как показано в листинге 13-29.

Листинг 13-29: Ограничение роутов при помощи HTTP методов
...
public static void RegisterRoutes(RouteCollection routes) {
	routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
	new { controller = "Home", action = "Index", id = UrlParameter.Optional },
	new { controller = "^H.*", action = "Index|About",
		httpMethod = new HttpMethodConstraint("GET") },
	new[] { "URLsAndRoutes.Controllers" });
}
...

Формат для указания ограничения при помощи HTTP метода немного странный. Не имеет никакого значения, какое имя мы дали свойству, поскольку мы присвоили его экземпляру класса HttpMethodConstraint. В листинге мы назвали наше свойство для ограничения HttpMethod, чтобы его можно было отличить от других видов ограничений.

Примечание

Возможность ограничения роутов при помощи HTTP методов не связана с возможностью ограничения методов действия при помощи таких атрибутов, как HttpGet и HttpPost. Ограничения роутов обрабатываются гораздо раньше в конвейере запросов, и они определяют имена контроллера и действия, необходимых для обработки запроса. Атрибуты методов действия используются для определения, какие конкретные методы действий будут использоваться для обработки запроса контроллером. Мы подробно расскажем о том, как работать с различными видами HTTP методов (в том числе таким, как PUT и DELETE), в главе 14.

Мы передаем имена нужных HTTP методов в виде строковых параметров в конструктор класса HttpMethodConstraint. В листинге мы ограничили роут запросом GET, но мы могли бы легко добавить поддержку других методов, например:

...
httpMethod = new HttpMethodConstraint("GET", "POST") },
...

Юнит тестирование: ограничение роутов

При тестировании ограничения роутов важно проверить как URL, которые будут соответствовать роутам, так и URL, которые вы пытаетесь исключить, что можно сделать с помощью вспомогательных методов, представленных в начале главы. Вот изменения тестового метода TestIncomingRoutes, который мы использовали для проверки роута, определенного в листинге 13-29:

...
[TestMethod]
public void TestIncomingRoutes() {
	TestRouteMatch("~/", "Home", "Index");
	TestRouteMatch("~/Home", "Home", "Index");
	TestRouteMatch("~/Home/Index", "Home", "Index");
	TestRouteMatch("~/Home/About", "Home", "About");
	TestRouteMatch("~/Home/About/MyId", "Home", "About", new { id = "MyId" });
	TestRouteMatch("~/Home/About/MyId/More/Segments", "Home", "About",
		new {
			id = "MyId",
			catchall = "More/Segments"
		});
	TestRouteFail("~/Home/OtherAction");
	TestRouteFail("~/Account/Index");
	TestRouteFail("~/Account/About");
}
...

Определение пользовательского ограничения

Если стандартные ограничения не удовлетворяют ваши потребности, вы можете определить свои собственные ограничения путем реализации интерфейса IRouteConstraint. Чтобы продемонстрировать эту возможность, мы добавили в проект папку Infrastructure и создали новый файл класса UserAgentConstraint.cs, содержание которого показано в листинге 13-30.

Листинг 13-30: Создание пользовательского ограничения роутов
using System.Web;
using System.Web.Routing;
namespace UrlsAndRoutes.Infrastructure
{
	public class UserAgentConstraint : IRouteConstraint
	{
		private string requiredUserAgent;
		public UserAgentConstraint(string agentParam)
		{
			requiredUserAgent = agentParam;
		}
		public bool Match(HttpContextBase httpContext, Route route, string parameterName,
			RouteValueDictionary values, RouteDirection routeDirection)
		{
			return httpContext.Request.UserAgent != null &&
				httpContext.Request.UserAgent.Contains(requiredUserAgent);
		}
	}
}

Интерфейс IRouteConstraint определяет метод Match, который можно использовать для того, чтобы определить, было ли сделано нужное ограничение. Параметры метода Match обеспечивает доступ к запросу от клиента, роуту, который в настоящее время оценивается, имени параметра ограничения, сегментным переменным, извлеченным из URL, а также информации о том, является ли это запросом на проверку входящего или исходящего URL. В нашем примере мы проверяем значение свойства UserAgent запроса клиента, чтобы увидеть, если оно содержит значение, которое было передано в наш конструктор. Листинг 13-31 показывает пользовательское ограничение, которое используется для роута.

Листинг 13-31: Применение пользовательского ограничения к роуту
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes
{
	public class RouteConfig
	{
		public static void RegisterRoutes(RouteCollection routes)
		{
			routes.MapRoute("ChromeRoute", "{*catchall}",
			new { controller = "Home", action = "Index" },
			new
			{
				customConstraint = new UserAgentConstraint("Chrome")
			},
			new[] { "UrlsAndRoutes.AdditionalControllers" });
			routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
			new
			{
				controller = "Home",
				action = "Index",
				id = UrlParameter.Optional
			},
			new[] { "URLsAndRoutes.Controllers" });
		}
	}
}

В листинге мы ограничили первый роут так, что он будет соответствовать только запросы от браузеров, где строка пользовательского агента содержит Chrome. Если роут совпадет, то запрос будет отправлен методу действия Index в контроллере Home, определенном в папке AdditionalControllers, независимо от структуры и содержания URL, который был запрошен. Наш URL паттерн состоит только из переменной сегмента catchall, что означает, что значения переменных сегмента controller и action всегда будут браться из значений по умолчанию, а не из самого URL.

Второй роут будет соответствовать всем другим запросам и целевыми контроллерами в папке Controllers. Результат использования этих роутов заключается в том, что определенный вид браузера всегда останавливается на том же месте в приложении. Вы можете увидеть это на рисунке 13-12, где показан переход к приложению с помощью Google Chrome.

Рисунок 13-12: Переход к приложению при помощи Google Chrome

На рисунке 13-13 показан переход к приложению при помощи Internet Explorer.

Рисунок 13-13: Переход к приложению при помощи Internet Explorer

Примечание

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