Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
В прошлой теме были рассмотрены стандартный механизм маршрутизации в ASP.NET Core с помощью компонентов EndpointRoutingMiddleware и EndpointMiddleware. Еще один способ построения маршрутизации представляет компонент называется RouterMiddleware. При обработке запроса RouterMiddleware сравнивает запрашиваемый адрес Url с зарегистрированными маршрутами, и если один из маршрутов подходит, то вызывается обработчик этого маршрута.
Для добавления RouterMiddleware изменим класс Startup:
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Routing; namespace RoutingApp { public class Startup { public void Configure(IApplicationBuilder app) { // определяем обработчик маршрута var myRouteHandler = new RouteHandler(Handle); // создаем маршрут, используя обработчик var routeBuilder = new RouteBuilder(app, myRouteHandler); // само определение маршрута - он должен соответствовать запросу {controller}/{action} routeBuilder.MapRoute("default", "{controller}/{action}"); // строим маршрут app.UseRouter(routeBuilder.Build()); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } // собственно обработчик маршрута private async Task Handle(HttpContext context) { await context.Response.WriteAsync("Hello ASP.NET Core!"); } } }
В методе Configure()
происходит настройка маршрута и определение обработчика, который будет обрабатывать этот запрос по этому маршруту:
var myRouteHandler = new RouteHandler(Handle); var routeBuilder = new RouteBuilder(app, myRouteHandler); routeBuilder.MapRoute("default", "{controller}/{action}"); app.UseRouter(routeBuilder.Build());
Класс RouteHandler представляет встроенный обработчик маршрута. В качестве параметра в него передается делегат RequestDelegate,
который и будет обрабатывать запрос. В данном случае на место делегата передается ссылка на метод Handle()
. Метод, который представляет делегат
RequestDelegate, должен в качестве параметра принимать контекст запроса HttpContext и возвращать объект Task. В данном случае этот метод просто направляет в выходной поток
строку "Hello ASP.NET Core!"
Для построения маршрута применяется объект RouteBuilder. В его конструктор передается сервис IApplicationBuilder, который в данном случае мы можем
получить из параметра метода Configure()
, и вышеопределенный обработчик маршрута.
Далее идет собственно определение самого маршрута в методе routeBuilder.MapRoute()
. В данном случае "default" задает имя маршрута, а строка
"{controller}/{action}"
представляет шаблон маршрута - некоторый шаблон Url, с которым будет сопоставляться
запрошенный адрес URL. Шаблон URL может состоять из одного и более сегментов. Если в шаблоне используется несколько сегментов, то они разделяются слешами.
Каждый такой сегмент шаблона содержит параметр. Эти параметры называются параметрами маршрута. Каждый параметр заключается в фигурные скобки. В данном случае это параметры controller и action. Но вообще параметры не обязательно должны иметь именно такие названия, они могут иметь различные имена, включающие любые алфавитно-цифровые символы.
И последняя строка подключает RouterMiddleware в конвейер обработки запроса:
app.UseRouter(routeBuilder.Build());
Вызов routeBuilder.Build()
возвращает объект IRouter, который затем переходит в RouterMiddleware и используется для обработки запросов.
После этого обработчик маршрута готов к использованию и может участвовать в обработке входящих запросов.
При этом после этого вызова могут идти другие компоненты middleware, которые могут участвовать в обработке запроса. Так, в данном случае в конце вызывается метод
app.Run()
, который отправляет в выходной поток строку "Hello World".
Теперь запустим приложение на выполнение. По умолчанию приложение открывается по адресу типа http://localhost:xxxx/ - то есть название домена и порта.
При этом надо учитывать, что RouterMiddleware не передает выполнение запроса дальше, если хотя бы один из определенных маршрутов совпал со строкой запроса.
Когда к приложению, которое использует маршруты, приходит запрос, то начинается процесс URL matching - процесс сопоставления строки запроса URL маршрутам. Причем в приложении может использоваться множество маршрутов. И RouterMiddleware последовательно перебирает все маршруты и сравнивает их шаблон с запрошенным адресом URL. Если шаблон маршрута и строка URL совпадают, то для обработки запроса выбирается обработчик данного маршрута. Если обработчик установлен, то он вызывается, и далее другие маршруты не вызываются. Если шаблон маршрута и запрошенный адрес URL не совпадают, тогда вызывается следующий маршрут.
Если все маршруты были вызваны, но обработчик для запроса так и не был найден, RouterMiddleware вызывает следующий в конвейере обработки запроса компонент middleware.
Так, в данном случае запрошенный URL выглядит следующим образом: http://localhost:59406
. Этот URL не имеет сегментов после домена и порта
(домен и порт в сегментной структуре не учитываются).
А наш единственный маршрут задает шаблон из двух сегментов: {controller}/{action}
. То есть в процессе URL matching компонент
RouterMiddleware не найдет нужный маршрут и просто передаст выполнение запроса дальше методу app.Run()
, который и выведет строку "Hello World!".
Теперь обратимся к нашему приложению с другим запросом http://localhost:59406/home/index:
В этом случае строка URL уже имеет два сегмента: home/index
. Теперь система маршрутизации сможет сопоставить адрес запроса с единственным
маршрутом, так как адрес запрос также состоит из двух сегментов. Для инкапсулирования всей информации о маршрутизации в RouterMiddleware создается объект RouteContext
Для полного понимания процесса мы можем обратиться к исходным кодам классов RouteHandler и RouterMiddleware. Так, при удачном сопоставлении запроса определенному маршруту во встроенном классе RouteHandler у контекста маршрутизации устанавливается свойство Handler. И этому свойству присваивается тот делегат RequestDelegate, который мы передаем через объект RouteHandler:
public class RouteHandler : IRouteHandler, IRouter { private readonly RequestDelegate _requestDelegate; public RouteHandler(RequestDelegate requestDelegate) { _requestDelegate = requestDelegate; } // остальное содержимое класса public Task RouteAsync(RouteContext context) { context.Handler = _requestDelegate; return TaskCache.CompletedTask; } }
И затем в методе Invoke класса RouterMiddleware вызывается этот делегат:
public class RouterMiddleware { // остальное содержимое public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router); // проверка соответствия строки запроса маршруту await _router.RouteAsync(context); // если сопоставление завершилось неудачно // и делегат не установлен, if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); // вызываем следующий компонент middleware await _next.Invoke(httpContext); } else { var routingFeature = new RoutingFeature() { RouteData = context.RouteData }; httpContext.Request.RouteValues = context.RouteData.Values; httpContext.Features.Set<IRoutingFeature>(routingFeature); await context.Handler(context.HttpContext); } } // остальное содержимое класса }
Поэтому при удачном сопоставлении с запросом система маршрутизации вызовет делегат RequestDelegate, который в данном случае указывает на метод Handle()
и который отправит в ответ на запрос строку "Hello ASP.NET Core".