📜 ⬆️ ⬇️

ASP.NET 5 Middleware or where did my HTTP module go?

The new version of ASP.NET 5 is rewritten almost from scratch and includes significant changes from previous versions. One of the biggest changes is the processing pipeline (HTTP Pipeline). This article describes how these changes affect the design and implementation of components that were previously presented as Http modules.

The behavior of HTTP modules used to be similar to the behavior of request filters, up to ASP.NET 5. This is functionality that can be implemented in the request pipeline and describe some task to be performed, for example, to respond to an event in an application. Modules are used for authentication, global error handling, and logging. They are also often used to intercept and modify a server response, such as removing spaces or compressing. They implement the IHttpModule interface, which is defined in the System.Web assembly, which, in turn, is not part of the new ASP.NET.

Basically, the registration of the HttpModule-i in the code is added as an event handler in Global.asax, or an assembly is created for registration in the web.config .

What is Middleware?


The definition of “Middleware” varies greatly, but in the context of ASP.NET 5, perhaps the most accurate definition is given in the Owin specification:
')
Cross-cutting components that form a pipeline between a server and an application that inspects, routes or modifies a request and response for a specific purpose.

Original:
Through the components of a specific purpose

Very similar to the description of a traditional HTTP module or handler (handler-a).

Access to the request pipeline in an ASP.NET 5 application is provided in the Startup class (Startup.cs file), it is also the entry point to the application itself. The Startup class includes the Configure method, which takes an IApplicationBuilder as a parameter. The IApplicationBuilder interface ( app parameter) provides a number of extension-methods by which various components can be connected to the request pipeline. The main difference between this and the previous implementation of the HTTP pipeline is that the previous approach was based on events, and now it has been replaced by a composite model (components are added one by one). Also, in the new ASP.NET, the order in which these components are added is important. In the basic template of the MVC application, there are already some components, and from the comments you can understand their purpose:
// 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?}"); }); 


As we can see, the methods by which Identity, MVC, and static files are added are the IApplicationBuilder extension-methods. When adding a new middleware handler, which will be described in this article, I will also create an extension method to add it to the pipeline.

Middleware example


Our sample middleware handler will do 2 things: measure the processing time of the request, and also add this value to the outgoing HTML and response headers. This is even the whole 3 things. In any case, this example will illustrate the 2 most common scenarios that are described in many articles like "How to write your own HTTP module" - changing the server response and adding headers. Handler Code:
 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 = @"<footer><div id=""process"">Page processed in {0} milliseconds.</div>"; responseBody = responseBody.Replace("<footer>", 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); } } } } } } } } 


We have a private field of type RequestDelegate , which stores the delegate that is passed to the constructor. The type RequestDelegate itself encapsulates the method that the HttpContext accepts and returns Task :
 public delegate Task RequestDelegate(HttpContext context); 


The HttpContext object is similar to the same from previous versions of ASP.NET in that it provides access to the request, response, etc., but it is a completely different beast and it is much easier.

In ASP.NET 5, middleware is an instance of Func <RequestDelegate, RequestDelegate> is the delegate that takes RequestDelegate as a parameter and returns RequestDelegate . And, as described above, RequestDelegate is a function that returns a function, and this is the principle by which the processing pipeline is built — each part of the intermediate layer, chained to another part, is responsible for passing on to the next processing in the chain (if necessary).

The Invoke method will be invoked at run time based on the convention. This is the place where the processing takes place in your part of the middleware, and the place where you give control to the next component in the pipeline if necessary. It is possible that you do not need to transfer control further, but instead stop execution, for example, if it is an authentication component that determines that the current user does not have the appropriate rights. Control is passed to the next component by calling await _next (context) . The code that you place before and will be executed, for example, in our case, we create a stopwatch and start it. In addition, we have access to the answer, which is implemented as a stream of data. Then the next component is called, which in turn calls the next one and so on. Then control is passed back through the component chain and the code that was added after the await _next (context) call is executed . It is in this block of code in our example that the response body changes to include HTML with elapsed time in the footer of the page. And then a header with the same value is added.

Wedging into conveyor


The next step is to include the component in the processing pipeline. As described above, this can be done using the extesion method, for example:
 namespace ASPNET5Test.MiddleWare { public static class BuilderExtensions { public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<MyMiddleware>(); } } } 


This is a simple example of a wrapper around the standard UseMiddleware method, which can now be found in Microsoft.AspNet.Builder .
Well, the last step is to call our method in the Startup class.
 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { app.UseMyMiddleware(); // ... 


When you first start the site takes a lot of time - almost 2 seconds, which can be seen at the bottom of the page:


Well, with the help of, for example, Developer Tools in Chrome, you can see the headers:


Conclusion


This article introduced the replacement of traditional HTTP modules and showed how to create and implement an instance of such a handler in an application. This article is based on the ASP.NET 5 Beta 3 version and the concepts that are illustrated here are subject to change.

Source: https://habr.com/ru/post/255463/


All Articles