[Перевод] ASP.NET 5 Middleware или куда же пропал мой Http модуль?

Новая версия ASP.NET 5 переписана почти с нуля и включает в себя существенные изменения по сравнению с предыдущими версиями. Одно из самых больших изменений — это конвейер обработки (HTTP Pipeline). В этой статье описано, как эти изменения влияют на проектирование и внедрение компонентов, которые раньше были представлены как Http модули.Поведение Http модулей раньше было схожим с поведением фильтров запросов, вплоть до ASP.NET 5. Это функционал, который можно внедрить в конвейер запросов и описать некоторую задачу для выполнения, например, отреагировать на событие в приложении. Модули используют для аутентификации, глобальной обработки ошибок и логгирования. Также их часто используют для перехвата и изменения серверного ответа, например, удаления пробелов или компрессии. Они реализуют интерфейс IHttpModule, который определен в сборке System.Web, которая, в свою очередь, не является частью нового ASP.NET.В основном регистрация HttpModule-я в коде добавляется как обработчик события в Global.asax или же создается сборка для регистрации в web.config.

Что такое Middleware? Определение «Middleware» сильно варьируется, но в контексте ASP.NET 5, пожалуй, самое точное определение дано в спецификации Owin: Путь через компоненты, которые образуют конвейер между сервером и приложением, которое выполняет инспекцию, маршрутизацию или изменении запроса и ответа с определенной целью.

Оригинал: Pass through components that form a pipeline between a server and application to inspect, route, or modify request and response messages for a specific purpose

Очень похоже на описание традиционного Http модуля или обработчика (handler-a).

Доступ к конвейеру запросов в приложении ASP.NET 5 предоставляется в классе Startup (файл Startup.cs), он же является точкой входа в само приложение. Класс Startup включает в себя метод Configure, который принимает IApplicationBuilder в качестве параметра. Интерфейс IApplicationBuilder (параметр app) предоставляет ряд extension-методов, с помощью которых различные компоненты могут быть подключены к конвейеру запросов. Основное различие между этой и предыдущей реализацией HTTP конвейера является то, что прежний подход базировался на событиях, а сейчас он был заменен на композитную модель (компоненты добавляются один за другим). Также, в новом ASP.NET важен порядок, в котором добавляют эти компоненты. В базовом шаблоне приложения MVC уже имеются некоторые компоненты, и по комментариям можно понять их назначение:

// Add static files to the request pipeline. app.UseStaticFiles ();

// Add cookie-based authentication to the request pipeline. app.UseIdentity ();

// Add MVC to the request pipeline. app.UseMvc (routes => { routes.MapRoute ( name: «default», template:»{controller}/{action}/{id?}», defaults: new { controller = «Home», action = «Index» });

// Uncomment the following line to add a route for porting Web API 2 controllers. // routes.MapWebApiRoute («DefaultApi», «api/{controller}/{id?}»); }); Как мы видим, методы, с помощью которых добавлены Identity, MVC и статические файлы — extension-методы IApplicationBuilder. При добавлении нового промежуточного обработчика, который будет описан в этой статье, я также создам extension-метод для добавления его к конвейеру.

Пример Middleware Наш пример промежуточного обработчика будет делать 2 вещи: измерять время обработки запроса, а также добавлять это значения в исходящий HTML и заголовки ответа. Это даже целые 3 вещи. В любом случае, этот пример будет иллюстрировать 2 самых распространенных сценария, что описаны во множестве статей типа «Как написать свой Http модуль» — изменение ответа сервера и добавление заголовков. Код обработчика: using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using System.Diagnostics; using System.IO; using System.Threading.Tasks;

namespace WebApplication1.MiddleWare { public class MyMiddleware { RequestDelegate _next;

public MyMiddleware (RequestDelegate next) { _next = next; }

public async Task Invoke (HttpContext context) { var sw = new Stopwatch (); sw.Start ();

using (var memoryStream = new MemoryStream ()) { var bodyStream = context.Response.Body; context.Response.Body = memoryStream; await _next (context);

var isHtml = context.Response.ContentType?.ToLower ().Contains («text/html»); if (context.Response.StatusCode == 200 && isHtml.GetValueOrDefault ()) { { memoryStream.Seek (0, SeekOrigin.Begin); using (var streamReader = new StreamReader (memoryStream)) { var responseBody = await streamReader.ReadToEndAsync (); var newFooter = @»

Page processed in {0} milliseconds.
»; responseBody = responseBody.Replace (»
», string.Format (newFooter, sw.ElapsedMilliseconds)); context.Response.Headers.Add («X-ElapsedTime», new[] { sw.ElapsedMilliseconds.ToString () }); using (var amendedBody = new MemoryStream ()) using (var streamWriter = new StreamWriter (amendedBody)) { streamWriter.Write (responseBody); amendedBody.Seek (0, SeekOrigin.Begin); await amendedBody.CopyToAsync (bodyStream); } } } } } } } } У нас есть приватное поле типа RequestDelegate, в котором хранится делегат, что передан конструктору. Сам тип RequestDelegate инкапсулирует метод, которые принимает HttpContext и возвращает Task:

public delegate Task RequestDelegate (HttpContext context); Объект HttpContext похож на такой же из предыдущих версий ASP.NET тем, что он обеспечивает доступ к запросу, ответа и т.д., но это совсем другой зверь и он намного легче.

В ASP.NET 5 middleware это экземпляр Func  — делегат, который принимает RequestDelegate в качестве параметра и возвращает RequestDelegate. И, как описано выше, RequestDelegate это функция, которая возвращает функцию, и это принцип, по которому построен конвейер обработки — каждая часть промежуточного слоя будучи прикованной к другой части, отвечает за передачу на обработку следующему в цепи (при необходимости).

Метод Invoke будет вызываться во время выполнения, базируясь на основе конвенции. Это и есть место, где происходит обработка в вашей части middleware, и место, где вы отдаете контроль следующему компоненту в конвейере в случае необходимости. Возможен случай, когда не нужно передавать контроль дальше, а вместо этого нужно остановить выполнение, например, если, это компонент аутентификации, который определяет, что текущий пользователь не имеет соответствующих прав. Управление передается следующему компоненту путем вызова await _next (context). Код, который вы размещаете до этого и будет выполняется, например, в нашем случае, мы создаем секундомер и запускаем его. Кроме того, у нас есть доступ к ответу, который реализуется в виде потока данных (stream). Затем вызывается следующий компонент, который, в свою очередь, вызывает следующий и так далее. Затем управление передается обратно по цепочке компонентов и выполняется код, который был добавлен после вызова await _next (context). Именно в этом блоке кода в нашем примере и изменяется тело ответа, чтобы включить HTML с истекшим временем в footer страницы. И тогда же, добавляется заголовок с этим же значением.

Вклинивание в конвейер Следующий шаг — это включить компонент в конвейер обработки. Как было описано выше это можно сделать с помощью extesion-метода, например: namespace ASPNET5Test.MiddleWare { public static class BuilderExtensions { public static IApplicationBuilder UseMyMiddleware (this IApplicationBuilder app) { return app.UseMiddleware(); } } } Это простой пример обёртки вокруг стандартного метода UseMiddleware, который сейчас можно найти в Microsoft.AspNet.Builder.Ну и последний шаг — вызов нашего метода в классе Startup.

public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { app.UseMyMiddleware ();

// … При первом запуске сайта уходит довольно много времени — почти 2 секунды, что можно увидеть внизу страницы: c11be89e2c2d4126b63c91a5cb77fcde.png

Ну и с помощью, например, Developer Tools в Chrome можно посмотреть заголовки: f737386d69ab4206bc0d5253bd9036ae.png

Заключение В этой статье была представлена замена традиционным Http модулям и показано как создать и внедрить в приложение экземпляр такого обработчика. Эта статья базируется на версии ASP.NET 5 Beta 3 и концепции, что здесь проиллюстрированы могут измениться.

© Habrahabr.ru