В преддверии курса "C# ASP.NET Core разработчик" приглашаем вас записаться на открытый урок по теме "Логирование и трейсинг запросов в asp.net core".
А пока делимся с вами традиционным полезным переводом.
Этой статья раскрывает концепции 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 для дальнейшей обработки.
При необходимости может завершить (замкнуть) конвейер запросов.
Выполняется в том порядке, в котором он был добавлены в конвейер.
Надеюсь, вы что-нибудь для себя почерпнули из этой статьи! Удачи вам в обучении!
Узнать подробнее о курсе и записаться на открытый урок можно здесь.
devopg
" return app.UseMiddleware();"
1) как он понимает что LogURLMiddleware определенной структуры, типа? нету ни наследования интерфейса, ни класса.
2) соответственно что говорит статический анализатор об этой конструкции?
AlexZyl
Утиная типизация.
Фреймворк ожидает, что зарегистрированный тип имеет конструктор и метод InvokeAsync с нужной сигнатурой.