Ограничение маршрутизации
104ASP.NET --- ASP.NET MVC 5 --- Ограничение маршрутизации
В предыдущих статьях было указано, что шаблоны URL являются консервативными при сопоставлении с сегментами и либеральными в отношении сопоставления содержимого сегментов. В нескольких предшествующих статьях объяснялись различные приемы управления степенью консерватизма - увеличение или уменьшение количества сегментов с использованием стандартных значений, необязательных переменных и т.п.
Теперь наступило время посмотреть на то, каким образом можно управлять либерализмом при сопоставлении содержимого сегментов URL: как ограничить набор URL, с которыми будет сопоставляться маршрут. Получив контроль над обоими этими аспектами поведения маршрута, мы сможем создавать схемы URL, которые выражаются с очень высокой точностью.
Ограничение маршрута с использованием регулярного выражения
Первый прием, который мы рассмотрим - ограничение маршрута с использованием регулярных выражений. Ниже показан пример:
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "MyRoute",
url: "{controller}/{action}/{id}/{*catchcall}",
defaults: new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
},
namespaces: new[] { "UrlsAndRoutes.Controllers" },
constraints: new { controller = "^H.*" });
}
}
}
Ограничения определяются путем их передачи в качестве параметра методу MapRoute(). Подобно стандартным значениям, ограничения выражаются в виде анонимного типа, свойства которого соответствуют именам ограничиваемых переменных сегментов. В этом примере было применено ограничение посредством регулярного выражения, которое соответствует только тем URL, в которых значение переменной controller начинается с буквы "H".
Стандартные значения используются перед проверкой ограничений. Таким образом, например, при запросе URL вида / применяется стандартное значение для controller, которым является Home. Затем проверяются ограничения, и поскольку значение controller начинается с "H", стандартный URL будет соответствовать маршруту.
Ограничение маршрута набором специфических значений
Регулярные выражения можно использовать для такого ограничения маршрута, чтобы к совпадению приводили только специфические значения для сегментов URL. Это делается с помощью символа |, как показано в примере ниже:
// ...
constraints: new { controller = "^H.*", action = "^Index$|^CustomVariable$" });
Это ограничение разрешает маршруту соответствовать только URL со значением сегмента action, равным Index или CustomVariable. Ограничения применяются совместно, так что ограничения, наложенные на значение переменной action, комбинируются с ограничениями, наложенными на значение переменной controller. Это значит, что маршрут в примере будет соответствовать только таким URL, в которых значение переменной controller начинается с буквы "H", а значением переменной action является Index или CustomVariable.
Итак, теперь вы видите, что понимается под созданием очень точных маршрутов.
Ограничение маршрута с использованием методов HTTP
Маршруты можно ограничивать так, чтобы они соответствовали URL, только когда запрос производится с использованием специфичного метода HTTP, что демонстрируется в примере ниже:
// ...
constraints: new
{
controller = "^H.*",
action = "^Index$|^CustomVariable$",
httpMethod = new HttpMethodConstraint("GET")
});
Формат для указания ограничения по методу HTTP выглядит несколько странно. Имя, выбираемое для свойства, роли не играет, т.к. свойству присваивается экземпляр класса HttpMethodConstraint. В примере свойство названо httpMethod, чтобы помочь отличить его от ограничений на основе значений, которые были определены ранее.
Возможность ограничения маршрутов по методу HTTP не имеет отношения к возможности ограничения методов действий с помощью таких атрибутов, как HttpGet и HttpPost. Ограничения маршрутов обрабатываются намного раньше в конвейере запросов, и они определяют имя контроллера и действие, требуемое для обработки запроса. Атрибуты методов действий используются для определения, какой конкретный метод действия будет применяться для обслуживания запроса контроллером.
Имена методов HTTP, которые должны поддерживаться, передаются в виде строковых параметров конструктору класса HttpMethodConstraint. В примере выше мы ограничили маршрут запросами GET, но очень просто добавить поддержку и других методов, например:
// ...
constraints: new
{
controller = "^H.*",
action = "^Index$|^CustomVariable$",
httpMethod = new HttpMethodConstraint("GET", "POST")
});
Модульное тестирование: ограничение маршрутов
При тестировании ограниченных маршрутов важно проверить как URL, которые должны соответствовать, так и URL, которые вы попытались исключить:
// ...
[TestMethod]
public void TestIncomingRoutes()
{
TestRouteMatch("~/", "Home", "Index");
TestRouteMatch("~/Home", "Home", "Index");
TestRouteMatch("~/Home/Index", "Home", "Index");
TestRouteMatch("~/Home/CustomVariable", "Home", "CustomVariable");
TestRouteMatch("~/Home/Index/All", "Home", "Index");
TestRouteMatch("~/Home/Index/All/Delete", "Home", "Index",
new { id = "All", catchcall = "Delete" });
TestRouteMatch("~/Home/Index/All/Delete/Insert", "Home", "Index",
new { id = "All", catchcall = "Delete/Insert" });
TestRouteFail("~/Home/About");
TestRouteFail("~/Admin/Index");
}
// ...
Использование ограничений на основе типов и значений
Инфраструктура MVC Framework содержит несколько встроенных ограничений, которые можно использовать, чтобы обеспечить соответствие URL маршруту на основе типов и значений переменных сегментов. В примере ниже демонстрируется применение одного из таких ограничений к конфигурации маршрутов в примере приложения:
// ...
using System.Web.Mvc.Routing.Constraints;
namespace UrlsAndRoutes
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
// ...
constraints: new
{
controller = "^H.*",
action = "^Index$|^CustomVariable$",
httpMethod = new HttpMethodConstraint("GET", "POST"),
id = new RangeRouteConstraint(10, 20)
});
}
}
}
С помощью классов ограничений, определенных в пространстве имен System.Web.Mvc.Routing.Constraints, можно проверять, имеют ли переменные сегментов значения различных типов C#, а также выполнять другие базовые проверки. В примере выше использовался класс RangeRouteConstraint, который проверяет, что значение, предоставленное переменной сегмента, имеет тип int и находится в диапазоне между двумя границами - 10 и 20.
В таблице ниже приведено описание полного набора классов ограничений. Не у всех классов конструкторы принимают аргументы, поэтому имена классов представлены так, как они применяются для конфигурирования маршрутов. Пока что не обращайте внимания на колонку "Атрибут ограничения". Мы вернемся к ней при рассмотрении маршрутизации с помощью атрибутов в следующей статье.
Класс | Конструктор класса | Описание | Атрибут ограничения |
---|---|---|---|
AlphaRouteConstraint | AlphaRouteConstraint() | Соответствует символам английского алфавита, независимо от регистра (A-Z, a-z) |
alpha |
BoolRouteConstraint | BoolRouteConstraint() | Соответствует значению, которое может быть разобрано в bool |
bool |
DateTimeRouteConstraint | DateTimeRouteConstraint() | Соответствует значению, которое может быть разобрано в DateTime |
datetime |
DecimalRouteConstraint | DecimalRouteConstraint() | Соответствует значению, которое может быть разобрано в decimal |
decimal |
DoubleRouteConstraint | DoubleRouteConstraint() | Соответствует значению, которое может быть разобрано в double |
double |
FloatRouteConstraint | FloatRouteConstraint() | Соответствует значению, которое может быть разобрано в float |
float |
IntRouteConstraint | IntRouteConstraint() | Соответствует значению, которое может быть разобрано в int |
int |
LengthRouteConstraint | LengthRouteConstraint(len) LengthRouteConstraint(min, max) |
Соответствует значению с указанным количеством символов либо имеющему длину между min и max символов |
length(len), length(min, max) |
MaxRouteConstraint | MaxRouteConstraint(val) | Соответствует значению int, если оно меньше val |
max(val) |
LongRouteConstraint | LongRouteConstraint() | Соответствует значению, которое может быть разобрано в long |
long |
MaxLengthRouteConstraint | MaxLengthRouteConstraint(len) | Соответствует строке, содержащей не более len символов |
maxlength(len) |
MinRouteConstraint | MinRouteConstraint(val) | Соответствует значению int, если оно больше val |
min(val) |
MinLengthRouteConstraint | MinLengthRouteConstraint(len) | Соответствует строке, содержащей, по меньшей мере, len символов |
minlength(len) |
RangeRouteConstraint | RangeRouteConstraint(min, max) | Соответствует значению int, если оно находится между min и max |
range(min, max) |
Ограничения можно комбинировать для одной переменной сегмента, используя класс CompoundRouteConstraint, конструктор которого принимает в своем аргументе массив ограничений. В примере ниже с помощью этого средства к переменной сегмента id применяются сразу два ограничения, AlphaRouteConstraint и MinLengthRouteConstraint, обеспечивая соответствие с маршрутом только строковых значений, которые содержат лишь алфавитные символы и имеют длину, по меньше мере, шесть символов:
// ...
constraints: new
{
controller = "^H.*",
action = "^Index$|^CustomVariable$",
httpMethod = new HttpMethodConstraint("GET", "POST"),
id = new CompoundRouteConstraint(new IRouteConstraint[] {
new AlphaRouteConstraint(),
new MinLengthRouteConstraint(6)
})
});
// ...
Определение специального ограничения
Если для удовлетворения текущих потребностей стандартных ограничений не хватает, можно определить собственные ограничения, реализовав интерфейс IRouteConstraint. Для демонстрации этой возможности мы добавили в пример проекта папку Infrastructure и создали в ней новый класс UserAgentConstraint.cs, код которого приведен в примере ниже:
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 клиентского запроса значение, переданное конструктору. Использование специального ограничения в маршруте продемонстрировано в примере ниже:
// ...
using UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "GoogleChromeRoute",
url: "{*catchcall}",
defaults: new
{
controller = "Home",
action = "Index"
},
constraints: new
{
custom = new UserAgentConstraint("Chrome")
},
namespaces: new[] { "UrlsAndRoutes.AdditionalControllers" });
routes.MapRoute(
name: "MyRoute",
// ...
}
}
}
Первый маршрут в примере выше ограничен так, что он будет соответствовать только запросам, поступающим от браузера, строка user-agent которого содержит Chrome. Если маршрут соответствует, запрос будет отправлен методу действия Index() контроллера Home, определенного в папке AdditionalControllers, независимо от структуры и содержимого запрошенного URL. Наш шаблон URL состоит только из переменной общего захвата, т.е. значения для переменных сегментов controller и action будут всегда браться из стандартных значений, а не из самого URL.
Второй маршрут будет соответствовать всем остальным запросам и отправляться контроллерам из папки Controllers с учетом ограничений на основе типов и значений, примененных в предыдущем разделе. Эффект, производимый этими маршрутами заключается в том, что один вид браузеров всегда попадает в одно и то же место приложения; это можно видеть на рисунке ниже, где показан результат перехода к приложению в Google Chrome:
На рисунке ниже можно видеть результат перехода к приложению в Internet Explorer. (Обратите внимание на добавление третьего сегмента, который содержит шесть или более алфавитных символов, чтобы обеспечить соответствие URL второму маршруту, из-за ограничений, примененных в предыдущем разделе.)
По вполне понятным причинам предполагается, что вы не будете ограничивать свое приложение поддержкой браузеров только одного типа. Строка user-agent в примере применялась исключительно для демонстрации специальных ограничений маршрутов. Подход, при котором веб-сайты навязывают пользователям какие-либо предпочтения относительно браузеров, нельзя считать удачным.