ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?

31 March 2015 13:22

ASP.NET 5 has been largely rewritten from the ground up, and incorporates some radical changes when compared with previous versions of ASP.NET. One of the biggest changes is in the HTTP Pipeline. This article looks at how those changes impact the design and registration of plug and play components that used to be represented by HttpModules.

HttpModules act as request filters in ASP.NET versions prior to 5. They are reusable units of code that can be plugged into the request pipeline, and tasked with responding to events defined in the HttpApplication class as they are fired. The kind of things that modules are used for include managing authentication, global error handling and logging. Traditional forms authentication is implemented as a module, and I have written about using ELMAH, a component that logs details of unhandled exceptions, which is impemented as an HttpModule. Modules can also be used to intercept and modify the outbound response to remove white space or compress it, for example. Modules implement the IHttpModule interface, which is defined in System.Web, and that library is not part of the new ASP.NET.

HttpModule code can either be added to the relevant event handler in Global.asax, or more usually, created as class libraries and registered with the application in the web.config file.

What Is Middleware?

The definition of "Middleware" varies widely depending on its context, but in relation to ASP.NET 5, the definition provided by the OWIN specification is probably closest:

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.

That‘s also pretty much the description of traditional HttpModules and their kin, HttpHandlers.

Access to the request pipeline in an ASP.NET 5 application is provided in the Startup class (Startup.cs file), which is the entry point to the application itself. The Startup class includes a Configure method which takes anIApplicationBuilder type as a parameter. The IApplicationBuilder interface, an instance of which is represented by the parameter app, provides a number of extension methods by which components can be plugged in to the request pipeline. The major difference between this and the previous implementation of the HTTP pipeline is that the event-driven approach has been replaced with a composable model. In the new ASP.NET, the order in which components are added is important. The Beta 3.0 MVC Starter site template has some components added with comments that explain 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?}");
});

The methods by which Identity, MVC and static files management are added to the pipeline are extension methods on the IApplicationBuilder type. When adding the middleware that will be built in this article, I will also create an extension method for adding it to the pipeline.

The Example Middleware

The example middleware that I shall create will do two things: it will measure the time taken to process the request and it will add the value to both the outgoing html and as a custom response header. That might be three things. Anyhoo, the example will feature two of the most common scenarios illustrated in the plethora of existing "Build your own HttpModule" tutorials on the Internet - modifying the response output and adding headers. The code for the middleware class is as follows:

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);
                            }
                        }
                    }
                }
            }
        }
    }
}

A private field of type RequestDelegate is declared. This is used to store the RequestDelegate that is passed in to the constructor that takes a RequestDelegate. A RequestDelegate is a function that takes an HttpContextinstance and returns a Task:

public delegate Task RequestDelegate(HttpContext context);

The HttpContext object is similar to the one found in previous versions of ASP.NET in that it provdes access to the request and response etc, but it is a different animal altogether and much, much slimmer.

In ASP.NET 5, middleware is an instance of Func<RequestDelegate, RequestDelegate> - a delegate that takes aRequestDelegate as a parameter and returns a RequestDelegate. And as described above, the RequestDelegateis a function that returns a function, and it is in this way that the pipeline is built, with each piece of middleware being chained to the next and responsible for passing the processing on to the next in line (if appropriate).

The Invoke method is a convention-based method in that the runtime looks for a method with that name and will call it. It is where the action happens within your middleware, and it is where you pass control on to the next component in the pipeline if appropriate. Sometimes you won‘t want to pass control on - you will want to halt execution of the pipeline altogether, if, for example, you are writing a custom authentication middleware that determines that the current user should proceed no further. Control is passed to the next component by callingawait _next(context). Code that you place before this call is executed, and in this example that code creates aStopwatch instance and starts it. The code also gets a reference to the response content, which is implemented as a stream. Then the next component is called, which in turn calls the next component and so on. Then control is passed back up the chain of components and any code that has been added after the await _next(context) call is executed. It is in that block of code in this example that the response body is modified to include some HTML to display the elapsed time in milliseconds somewhere in the footer region of the page. At the same time, a custom header is added with the elapsed time as its value.

Plugging into the pipeline

The next step is to plug the component into the pipeline. As I mentioned earlier, this is achieved via an extension method on the IApplicationBuilder type:

using Microsoft.AspNet.Builder;

namespace ASPNET5Test.MiddleWare
{
    public static class BuilderExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<MyMiddleware>();
        }
    }
}

The result is a wrapper around the existing UseMiddleware<T> extension method (currently found in theMicrosoft.AspNet.Builder namespace - hence the using directive) into something a little prettier.

Finally, the extension method is called in the Configure method of the Startup class:

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

    // etc

When the site is run for the first time, the processing time takes a while - just under two seconds in this image - and is printed to the bottom of the screen (with a little CSS assistance):

And the developer tools in Chrome provide an easy way to confirm that the custom header was added:

Summary

In this article, I introduced the ASP.NET5 replacement to traditional HttpModules and showed how to create one and plug it in to the application pipeline. This article is based on Beta 3 of ASP.NET 5 and the concepts illustrated here are subject to change. I will endeavour to keep the article current in line with future releases.

时间: 2024-11-13 06:02:22

ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?的相关文章

[ASP.NET Core] Middleware

前言 本篇文章介绍ASP.NET Core里,用来处理HTTP封包的Middleware,为自己留个纪录也希望能帮助到有需要的开发人员. ASP.NET Core官网 结构 在ASP.NET Core里,每个从「浏览器传入」的HTTP Request封包,会被系统封装为「HttpRequest对象」,并且配置默认的HttpResponse对象.Session对象.ClaimsPrincipal对象...等等物件.接着将这些对象,封装成为一个「HttpContext对象」,用来提供ASP.NET

ASP.NET Core中Middleware的使用

ASP.NET 5中Middleware的基本用法 在ASP.NET 5里面引入了OWIN的概念,大致意思是将网站部署.服务器.中间组件以及应用分离开,这里提到的Middleware就是中间组件. 这里引用asp.net网站的介绍图 Middleware的作用有点类似于httpmodule,服务器接收到的请求都会传递到各个Middleware,多个Middleware形成一个处理管道. 由于不针对于特定的请求,所以Middleware的执行范围是在Application之外,这种模式很适合处理日

Adding Cache-Control headers to Static Files in ASP.NET Core

Thanks to the ASP.NET Core middleware pipeline, it is relatively simple to add additional HTTP headers to your application by using custom middleware. One common use case for this is to add caching headers. Allowing clients and CDNs to cache your con

[转]Publishing and Running ASP.NET Core Applications with IIS

本文转自:https://weblog.west-wind.com/posts/2016/Jun/06/Publishing-and-Running-ASPNET-Core-Applications-with-IIS#DoyouneedIIS? When you build ASP.NET Core applications and you plan on running your applications on IIS you'll find that the way that Core ap

ASP.NET Core集成微信登录

工具: Visual Studio 2015 update 3 Asp.Net Core 1.0 1 准备工作 申请微信公众平台接口测试帐号,申请网址:(http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login).申请接口测试号无需公众帐号,可以直接体验和测试公众平台所有高级接口. 1.1 配置接口信息 1.2 修改网页授权信息 点击“修改”后在弹出页面填入你的网站域名: 2  新建网站项目 2.1 选择ASP.NET Core

[ASP.NET MVC] ASP.NET Identity登入技术剖析

[ASP.NET MVC] ASP.NET Identity登入技术剖析 前言 ASP.NET Identity是微软所贡献的开源项目,用来提供ASP.NET的验证.授权等等机制.本篇文章介绍ASP.NET Identity在执行登入功能时,与浏览器.还有第三方验证服务之间的运作流程.主要为自己留个纪录,也希望能帮助到有需要的开发人员.(本篇内容大幅度简化了ASP.NET Identity的运作细节,用以传达登入功能的运作概念.实际ASP.NET Identity在运作的时候,比本篇说明的复杂很

Dependency injection in ASP.NET Core

原文 ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC con

12.ASP.NET Core 中间件组件

这篇文章中,我将带领大家一起详细学习:ASP.NET Core Middleware Components.这篇文章中,我将详细讨论下面几个问题: 什么是ASP.NET Core 中的中间件组件? ASP.NET Core应用程序中,在哪里来使用中间件组件? 怎样来配置ASP.NET Core 应用程序中的中间件组件? 使用中间件组件的例子有哪些? ASP.NET Core应用程序中,中间件组件执行的顺序是? 什么是ASP.NET Core中间件组件? ASP.NET Core中间件组件就是组装

深入理解asp.net里的HttpModule机制

刚工作的时候看<asp.net深入解析>,第一次知道HttpModule和HttpHandler.当时对我而言,它们不过就是两个新名词而已,仅仅知道工作原理但是理解的不深刻.随着经验的累积,逐渐发现它们对开发的重要性.现在回头再看一遍它们的实现机制,顺便参考一下其他资源做个透彻的读书笔记.1.asp.net的HTTP请求处理过程说明:(1).客户端浏览器向服务器发出一个http请求,此请求会被inetinfo.exe进程截获,然后转交给aspnet_isapi.dll进程,接着它又通过Http