Разбираемся с middleware в ASP.NET Core

от автора

В преддверии курса «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 для дальнейшей обработки.

  • При необходимости может завершить (замкнуть) конвейер запросов.

  • Выполняется в том порядке, в котором он был добавлены в конвейер.

Надеюсь, вы что-нибудь для себя почерпнули из этой статьи! Удачи вам в обучении!


Узнать подробнее о курсе и записаться на открытый урок можно здесь.

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/528692/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *