Эта статья раскрывает концепции Middleware в ASP.NET Core. К концу этой статьи вы получите четкое представление о следующих моментах:
- Что такое Middleware?
- Почему порядок расположения Middleware имеет значение?
- Методы Run, Use и Map.
- Как создать собственное Middleware?
- Как реализовать просмотр каталогов с помощью Middleware?
Что такое Middleware?
Middleware (промежуточное или связующее программное обеспечение) — это фрагмент кода в конвейере приложения, используемый для обработки запросов и ответов.
Например, у нас может быть middleware-компонент для аутентификации пользователя, middleware-компонент для обработки ошибок и еще один middleware-компонент для обслуживания статических файлов, таких как файлы JavaScript, CSS, разного рода изображения и т. д.
Middleware может быть встроенным как часть платформы .NET Core, добавляемым через пакеты NuGet или же написанным самим пользователем. Middleware-компоненты настраиваются в методе Сonfigure класса запуска приложения (Startup). Метод Configure выстраивает конвейер обработки запросов в ASP.NET Core приложении. Он состоит из последовательности делегатов запросов, вызываемых один за другим.
На рисунке ниже показано, как запрос обрабатывается middleware-компонентами.
Как правило, каждое middleware обрабатывает входящие запросы и передает выполнение следующему middleware для дальнейшей обработки.
Но middleware-компонент также может решить не вызывать следующую часть middleware в конвейере. Это называется замыканием (short-circuiting) или завершением конвейера запросов. Замыкание зачастую желательно, поскольку оно позволяет избежать ненужной работы. Например, если это запрос статического файла, такого как файл CSS, JavaScript, изображение и т. д., middleware-компонент для статических файлов может обработать и обслужить этот запрос, а затем замкнуть остальную часть конвейера.
Давайте создадим ASP.NET Core веб-приложение и рассмотрим конфигурацию middleware по умолчанию в методе Configure класса Startup.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
//This middleware is used reports app runtime errors in development environment.
app.UseDeveloperExceptionPage();
}
else
{
//This middleware is catches exceptions thrown in production environment.
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); //adds the Strict-Transport-Security header.
}
//This middleware is used to redirects HTTP requests to HTTPS.
app.UseHttpsRedirection();
//This middleware is used to returns static files and short-circuits further request processing.
app.UseStaticFiles();
//This middleware is used to route requests.
app.UseRouting();
//This middleware is used to authorizes a user to access secure resources.
app.UseAuthorization();
//This middleware is used to add Razor Pages endpoints to the request pipeline.
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
Фреймворк ASP.NET Core предоставляет встроенные middleware-компоненты, которые мы можем легко использовать, добавляя в метод Configure. Ознакомьтесь с документацией Microsoft для получения более подробной информации.
Упорядочение Middleware
Middleware-компоненты выполняются в том порядке, в котором они добавляются в конвейер, по этому следует проявлять осторожность и добавлять middleware в правильном порядке, иначе приложение может работать не так, как вы ожидаете. Порядок расположения middleware важен для безопасности, производительности и функциональности.
Следующие middleware-компоненты предназначены для стандартных сценариев приложений и расположены в рекомендуемом порядке:
Первый middleware-компонент в конфигурации получил запрос, изменил его (при необходимости) и передал управление следующему middleware. Точно так же первый middleware-компонент выполняется последним при обработке ответа, если мы возвращаем обратно эхо. Вот почему делегаты обработки исключений должны вызываться на самых ранних этапах конвейера - чтобы они могли проверить результат и отобразить возможное исключение в удобном для браузера и клиента виде.
Методы Run, Use и Map
app.Run()
Этот метод добавляет middleware-компонент в виде Run[Middleware], который выполнится в конце конвейера. Как правило, он действует как замыкающее middleware и добавляется в конце конвейера запросов, поскольку не может вызывать следующий middleware-компонент.
app.Use()
Этот метод используется для конфигурирования нескольких middleware. В отличие от app.Run(), мы можем включить в него параметр next, который вызывает следующий делегат запроса в конвейере. Мы также можем замкнуть (завершить) конвейер, не вызывая параметр next.
Давайте рассмотрим следующий пример с app.Use() и app.Run() и проанализируем результат/ответ:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Before Invoke from 1st app.Use()\n");
await next();
await context.Response.WriteAsync("After Invoke from 1st app.Use()\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Before Invoke from 2nd app.Use()\n");
await next();
await context.Response.WriteAsync("After Invoke from 2nd app.Use()\n");
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from 1st app.Run()\n");
});
// the following will never be executed
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from 2nd app.Run()\n");
});
}
Первый делегат app.Run() завершает конвейер. В этом примере будет запущен только первый делегат («Hello from 1st app.Run()»), а запрос никогда не достигнет второго метода Run.
app.Map()
Этот метод расширения используются как условное обозначение для ветвления конвейера. Map разветвляет конвейер запросов на основе пути запроса. Если путь запроса начинается с указанного пути, ветвь выполняется.
Давайте рассмотрим следующий пример с app.Map() и проанализируем результат/ответ:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Map("/m1", HandleMapOne);
app.Map("/m2", appMap => {
appMap.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd app.Map()");
});
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from app.Run()");
});
}
private static void HandleMapOne(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 1st app.Map()");
});
}
В следующей таблице показаны запросы и ответы от localhost с использованием приведенного выше кода.
Request
|
Response
|
https://localhost:44362/
|
Hello from app.Run()
|
https://localhost:44362/m1
|
Hello from 1st app.Map()
|
https://localhost:44362/m1/xyz
|
Hello from 1st app.Map()
|
https://localhost:44362/m2
|
Hello from 2nd app.Map()
|
https://localhost:44362/m500
|
Hello from app.Run()
|
Создание собственного Middleware
Middleware обычно инкапсулируется в класс и предоставляется с помощью метода расширения. Middleware может быть создано с помощью класса с методом InvokeAsync() и параметром типа RequestDelegate в конструкторе. Тип RequestDelegate требуется для выполнения следующего middleware в последовательности.
Рассмотрим пример, в котором нам нужно создать собственное middleware для регистрации URL-адреса запроса в веб-приложении.
public class LogURLMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LogURLMiddleware> _logger;
public LogURLMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory?.CreateLogger<LogURLMiddleware>() ??
throw new ArgumentNullException(nameof(loggerFactory));
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"Request URL: {Microsoft.AspNetCore.Http.Extensions.UriHelper.GetDisplayUrl(context.Request)}");
await this._next(context);
}
}
public static class LogURLMiddlewareExtensions
{
public static IApplicationBuilder UseLogUrl(this IApplicationBuilder app)
{
return app.UseMiddleware<LogURLMiddleware>();
}
}
В методе Configure:
app.UseLogUrl();
Реализация просмотра каталогов с помощью Middleware
Просмотр каталогов позволяет пользователям вашего веб-приложения видеть собственно сам список каталогов и файлы.
Просмотр каталогов по умолчанию отключен из соображений безопасности.
Давайте рассмотрим пример, в котором мы хотим реализовать просмотр списка изображений в браузере из папки с изображениями в wwwroot. Middleware UseDirectoryBrowser может обрабатывать и обслуживать эти изображения для такого рода запросов, а затем замкнуть остальную часть конвейера.
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/images"
});
Резюме
Middleware в ASP.NET Core контролирует, как наше приложение отвечает на HTTP-запросы.
Таким образом, каждый middleware-компонент в ASP.NET Core:
- Имеет доступ как к входящим запросам, так и к отправляемым обратно ответам.
- Может просто передать запрос следующему middleware в конвейере.
- Может выполнять некоторую логику обработки и затем передавать этот запрос следующему middleware для дальнейшей обработки.
- При необходимости может завершить (замкнуть) конвейер запросов.
- Выполняется в том порядке, в котором он был добавлены в конвейер.
Надеюсь, вы что-нибудь для себя почерпнули из этой статьи! Удачи вам в обучении!