Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
За сопоставление запросов с конкретными адресами внутри приложения в ASP.NET Core отвечает система маршрутизации. Для работы с системой маршрутизации в ASP.NET Core создадим простой проект по типу Empty, который назовем RoutingApp.
Если мы обратимся к коду класса Startup, который создается по умолчанию, то мы можем увидеть там примитивную систему маршрутизации:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace RoutingApp { public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } } }
В частности, вызов
app.UseRouting();
Добавляет в конвейер обработки запроса компонент EndpointRoutingMiddleware. Система маршрутизации использует конечные точки (endpoints) для обработки запросов по определенным маршрутам. И компонент EndpointRoutingMiddleware как раз позволяет определить маршрут, который соответствует запрошенному адресу, и установить для его обработки конечную точку в виде объекта Microsoft.AspNetCore.Http.Endpoint, а также определить данные маршрута.
После того, как установлена конечная точка Endpoint, выполняется следующий вызов:
app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); });
Метод app.UseEndpoints() встраивает в конвейер обработки компонент EndpointMiddleware. Он принимает делегат с одним параметром типа Microsoft.AspNetCore.Routing.IEndpointRouteBuilder, у которого можно вызвать ряд методов для установки обработчика определенных маршрутов. В частности, метод MapGet() добавляет конечную точку для определеного маршрута по запросу типа GET и ее обработчик.
Например, в данном случае определена только одна конечная точка, которая сопоставляется с маршрутом "/" (обращение к корню веб-приложения) и в ответ на запрос отправляет строку "Hello World".
Таким образом, при обращении к корню веб-приложения будет создана конечная точка для обработки маршрута "/", и в бразуере мы увидим строку "Hello World":
При этом при обращении по всем других адреса, которые не соответствуют обрабатываемым маршрутам, например, по адресу "/index", мы увидим, что ресурс не найден. Так как для них не установлено конечных точек.
То есть в данном случае мы видим разделение системы маршрутизации на два этапа: установка конечной точки и обработка установленной конечной точки. Важно отметить, что между этими этапами мы можем внедрять в конвейер обработки запроса свои middleware и проверять, какая именно конечная точка выбрана (и выбрана ли) и при необходимости выполнять какие либо действия.
Для более детального ознакомления с внутренней логикой системы маршрутизаци можно обратиться к исходным кодам, которые можно найти на github в каталоге https://github.com/aspnet/AspNetCore/tree/master/src/Http/Routing/src.
Чтобы чуть более детально разобраться в маршрутизации, изменим класс Startup следующим образом:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using System.Diagnostics; namespace RoutingApp { public class Startup { public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.Use(async (context, next) => { // получаем конечную точку Endpoint endpoint = context.GetEndpoint(); if (endpoint != null) { // получаем шаблон маршрута, который ассоциирован с конечной точкой var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern?.RawText; Debug.WriteLine($"Endpoint Name: {endpoint.DisplayName}"); Debug.WriteLine($"Route Pattern: {routePattern}"); // если конечная точка определена, передаем обработку дальше await next(); } else { Debug.WriteLine("Endpoint: null"); // если конечная точка не определена, завершаем обработку await context.Response.WriteAsync("Endpoint is not defined"); } }); app.UseEndpoints(endpoints => { endpoints.MapGet("/index", async context => { await context.Response.WriteAsync("Hello Index!"); }); endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } } }
Теперь между вызовами методов app.UseRouting()
и app.UseEndpoints()
мы добавили собственный middleware, чтобы отследить
установленные данные. В этом middleware с помощью метода HttpContext.GetEndpoint()
можно получить конечную точку для обработки текущего запроса,
которая была установлена на предыдущем этапе EndpointRoutingMiddleware.
Endpoint endpoint = context.GetEndpoint();
Обратите внимание, что возможна ситуация, когда для обработки текущего запроса не будет установлена конечная точка, то есть она будет равна null, если текущий запрос не соответствует ни одному из маршрутов в EndpointMiddleware. То есть для определения конечной точки EndpointRoutingMiddleware использует маршруты из EndpointMiddleware.
Класс Endpoint не предоставляет много информации о конечной точке. В данном случае используется его свойство DisplayName, которое представляет имя конечной точки и обычно совпадает с маршрутом, обрабатываемым данной конечной точкой.
Куда больше информации предоставляет класс Microsoft.AspNetCore.Routing.RouteEndpoint, который наследуется от Endpoint и добавляет ряд дополнительных свойств.
В частности, в данном случае применяется свойство RoutePattern.RawText
. RoutePattern (объект одноименного класса RoutePattern) хранит всю информацию,
ассоциированную с маршрутом конечной точки. Далее свойство RawText
фактически хранит представление маршрута в текстовом виде, например, "/index" или "/".
В итоге, если конечная точка установлена, то выводим полученные данные и передаем управление следующему компоненту в конвейере, которым в данном случае является EndpointMiddleware.
var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern?.RawText; Debug.WriteLine($"Endpoint Name: {endpoint.DisplayName}"); Debug.WriteLine($"Route Pattern: {routePattern}"); // если конечная точка определена, передаем обработку дальше await next();
Если конечная точка для обработки текущего запроса не определена, то завершаем обработку запроса, отправляя пользователю некоторое сообщение:
else { Debug.WriteLine("Endpoint: null"); // если конечная точка не определена, завершаем обработку await context.Response.WriteAsync("Endpoint is not defined"); }
Собственно в этом одно из преимуществ системы маршрутизации в ASP.NET Core 3+: даже если конечная точка не определена, мы можем при необходимости сформировать ответ пользователю, не передавая запрос далее - в EndpointMiddleware.
И в конце EndpointMiddleware определяет обработчики для двух маршрутов - "/index" и "/".
Запустим приложение и обратимся по адресу "/index" - то есть по первому маршруту в EndpointMiddleware:
После этого в окне Output в Visual Studio мы можем увидеть диагностическую информацию: