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 purposeVery 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:
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.