Несмотря на то, что ASP.NET Core предлагает довольно большой перечень стандартных ограничений для параметров маршрутов, позволяющих покрыть, если не все 100, то 99,9% потребностей разработчика, всё же таких ограничений может оказаться недостаточно для работы и поэтому в ASP.NET Core мы можем самостоятельно создавать собственные ограничения. Рассмотрим как это можно сделать.
Интерфейс IRouteConstraint
Для создания собственного ограничения маршрута необходимо реализовать интерфейс IRouteConstraint
, который имеет всего один метод Match() со следующей сигнатурой:
Match(HttpContext, IRouter, String, RouteValueDictionary, RouteDirection)
здесь httpContext
— объект типа HttpContext
, содержащий сведения об HTTP-запросе.
route
— маршрутизатор IRouter
, к которому относится это ограничение.
routeKey
— имя проверяемого параметра.
values
— словарь RouteValueDictionary
, содержащий параметры для URL-адреса, в котором ключ — это имя параметра.
routeDirection
— объект RouteDirection
, указывающий, когда выполняется проверка ограничения: при обработке входящего запроса или при создании URL-адреса.
В качестве результата Match()
возвращает значение типа bool: true
, если запрос удовлетворяет данному ограничению маршрута, и false
, если не удовлетворяет. Таким образом, метод вызывается для каждого параметра маршрута и, если для всех параметров метод Match() вернул true, то запрос пользователя может быть сопоставлен с данным маршрутом (выполняется код конечной точки).
Посмотрим, как мы можем создать свое ограничение, используя интерфейс IRouteConstraint.
Создание собственных ограничений маршрутов
Создадим собственное ограничение маршрута, которое будет проверять все символы в верхнем регистре и сравнивать их с некоторой секретной строкой. Для начала, создадим класс, реализующий интерфейс IRouteConstraint
public class UpperCaseSecret : IRouteConstraint { private string _secret; public UpperCaseSecret(string secret) { _secret = secret.ToUpper(); } public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (!values.TryGetValue(routeKey, out var routeValue)) //если не смогли получить значение параметра { return false; } //если значение пустая строка или null string routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture); if (string.IsNullOrEmpty(routeValueString)) { return false; } //не нашли ни одного символа в верхнем регистре var upChars = routeValueString.Where(c => char.IsUpper(c)); if (upChars.Any() == false) { return false; } string secretCode = string.Concat(upChars); return secretCode == _secret; } }
В конструктор класса передпется секретное слово. Затем, в методе Match()
из параметра выбираются все символы в верхнем регистре с использованием метода расширения Linq Where
и полученное множество символов сравнивается с секретным словом. Если слова совпадают, то метод возвращает true
.
Чтобы применить пользовательское ограничение маршрута, тип ограничения необходимо зарегистрировать с помощью метода ConstraintMap
в контейнере DI следующим образом:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRouting(options => options.ConstraintMap.Add("UpperCaseSecret", typeof(UpperCaseSecret))); var app = builder.Build(); //здесь остальной код метода Main
Теперь применим наше ограничение маршрута для конечной точки:
app.MapGet("/tasks/{secretWord:UpperCaseSecret(hello)}", async (context) => { context.Response.ContentType = "text/html; charset=utf-8"; StringBuilder sb = new StringBuilder(); sb.AppendLine("<h1>Список задач</h1>"); sb.AppendLine("<table>"); sb.AppendLine("<tr><td>Id</td><td>Задача</td><td>Начало</td><td>Окончание</td></tr>"); foreach (ToDoTask task in toDoList) { sb.AppendLine($"<tr><td>{task.Id}</td><td>{task.Name}</td><td>{task.Start: dd:mm:yyyy}</td><td>{task.End: dd:mm:yyyy}</td></tr>"); } await context.Response.WriteAsync(sb.ToString()); });
здесь параметр secretWord
имеет ограничение UpperCaseSecret
. Если второй сегмент пути будет содержать символы с помощью которых мы сможем составить слово HELLO, то на страницу будет выведен список задач, который мы составляли в этой части. Посмотрим на результат:
Итого
Для создания собственных ограничений маршрутов мы должны реализоваться интерфейс IRouteConstraint
и зарегистрировать полученный тип ограничения с помощью метода ConstraintMap
в контейнере DI. В конструкторы классов, реализующих IRouteConstraint
можно передавать различные значения, которые будут влиять на результат выполнения метода IRouteConstraint.Match
.