ASP.NET Core Middleware

中間件(Middleware)是ASP.NET Core中的一個重要特性。所謂中間件就是嵌入到應用管道中用於處理請求和響應的一段代碼。ASP.NET Core Middleware能夠分爲兩種類型:html

Conventional Middleware

這種中間件沒有實現特定的接口或者繼承特定類,它更像是Duck Typing (你走起路來像個鴨子, 叫起來像個鴨子, 那麼你就是個鴨子)。有兩種表現形式:web

匿名方法

這種方式又稱爲內聯中間件(in-line middleware),可使用RunMapUse,MapWhen等擴展方法來實現。如:api

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });
    }
}

IApplicationBuilder的擴展方法:RunMapMapWhen
Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware),最終都會調用IApplicationBuilder接口中的Use(Func<RequestDelegate, RequestDelegate> middleware)方法來實現向請求處理管道中注入中間件,後面會對源碼作分析。mvc

自定義中間件類

這種形式利於代碼的複用,如:app

public class XfhMiddleware
{
    private readonly RequestDelegate _next;

    //在應用程序的生命週期中,中間件的構造函數只會被調用一次
    public XfhMiddleware(RequestDelegate next)
    {
        this._next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Do something...
        await _next(context);
    }
}

public static class XfhMiddlewareExtension
{
    public static IApplicationBuilder UseXfhMiddleware(this IApplicationBuilder builder)
    {
        // 使用UseMiddleware將自定義中間件添加到請求處理管道中
        return builder.UseMiddleware<XfhMiddleware>();
    }
}

 

將自定義中間件配置到請求處理管道中asp.net

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseXfhMiddleware();
}

IMiddleware

IMiddleware提供了強類型約束的中間件,其默認實現是MiddlewareFactory,接口定義以下:async

public interface IMiddleware
{
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

IMiddlewareFactory用於建立IMiddleware實例及對實例進行回收,接口定義:ide

public interface IMiddlewareFactory
{
    public IMiddleware Create (Type middlewareType);
    
    public void Release (IMiddleware middleware);
}

 

自定義IMiddleware類型中間件

public class MyMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        await next(context);
    }
}


public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

 

將中間件注入到請求處理管道:

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

 

使用IMiddleware類型的中間件須要在容器中進行註冊,不然拋異常,具體緣由下面分析:

 

 

將中間件注入到容器中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<MyMiddleware>();
    services.AddMvc();
}

 

一段警告

下面貼一段微軟文檔中的警告,大意是不要試圖去改變已發往客戶端的響應內容,不然可能會引起異常。實在是太懶了,不想翻譯就把原文貼出來了:

Warning

Don't call next.Invoke after the response has been sent to the client. Changes to HttpResponse after the response has started throw an exception. For example, changes such as setting headers and a status code throw an exception. Writing to the response body after calling next:

  • May cause a protocol violation. For example, writing more than the stated Content-Length.

  • May corrupt the body format. For example, writing an HTML footer to a CSS file.

HasStarted is a useful hint to indicate if headers have been sent or the body has been written to.


UseMiddleware

前面將自定義中間件注入到請求處理管道時用到了UseMiddleware方法,從方法簽名中能夠看到UserMiddleware能夠接受多個參數

public static class UseMiddlewareExtensions
{
    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app,  
            params object[] args);
   
    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, 
            Type middleware, params object[] args);
}

 

接下來咱們看下UserMiddleware方法的具體實現,因爲該方法代碼量較大,因此這裏只看其中的關鍵部分,方法總體流程以下:

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware,
    params object[] args)
{
    // IMiddleware類型
    if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
    {
        // IMiddleware doesn't support passing args directly since it's
        // activated from the container
        if (args.Length > 0)
        {
            throw new NotSupportedException(
                Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
        }
        return UseMiddlewareInterface(app, middleware);
    }
    
    // Conventional Middleware
    var applicationServices = app.ApplicationServices;
    return app.Use(next =>
    {
        // 判斷傳入的中間件是否符合約束
    });
}

 

  • 該方法首先判斷傳入的middleware是不是IMiddleware類型,若是是則調用UseMiddlewareInterface

從這段代碼中能夠看到IMiddlewareFactory負責建立並回收IMiddleware對象

public static class UseMiddlewareExtensions
{
    private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
    {
        return app.Use(next =>
        {
            return async context =>
            {
                // 從容器中獲取IMiddlewareFactory實例
                var middlewareFactory =
                    (IMiddlewareFactory) context.RequestServices.GetService(typeof(IMiddlewareFactory));
                if (middlewareFactory == null)
                {
                    // No middleware factory
                    throw new InvalidOperationException(
                 Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                }
                
                var middleware = middlewareFactory.Create(middlewareType);
                if (middleware == null)
                {
                    // The factory returned null, it's a broken implementation
                    throw new InvalidOperationException(
                        Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(),
                            middlewareType));
                }
                
                try
                {
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }
}

 

MiddlewareFactoryCreate方法中能夠看到,IMiddleware實例是從容器中獲取的,若容器中找不到則會拋出異常:

public class MiddlewareFactory : IMiddlewareFactory
{
    private readonly IServiceProvider _serviceProvider;
    
    public MiddlewareFactory(IServiceProvider serviceProvider)
    {
        this._serviceProvider = serviceProvider;
    }
    
    public IMiddleware Create(Type middlewareType)
    {
        return ServiceProviderServiceExtensions.GetRequiredService(this._serviceProvider, middlewareType) as IMiddleware;
    }
    
    public void Release(IMiddleware middleware)
    {
    }
}

 

  • 如果Conventional Middleware則判斷傳入的middleware是否符合約束

首先判斷傳入的middleware中是否僅包含一個名稱爲Invoke或InvokeAsync的公共實例方法

// UseMiddlewareExtensions類中的兩個常量
internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";

// UserMiddleware方法
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m =>
    string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
    || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray();
if (invokeMethods.Length > 1)
{
    throw new InvalidOperationException(
        Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
}
if (invokeMethods.Length == 0)
{
    throw new InvalidOperationException(
        Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName,
            middleware));
}

 

其次判斷方法的返回類型是不是Task

var methodInfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
    throw new InvalidOperationException(
        Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName,
            InvokeAsyncMethodName, nameof(Task)));
}

 

而後再判斷,方法的第一個參數是不是HttpContext類型:

var parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
    throw new InvalidOperationException(
        Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName,
            nameof(HttpContext)));
}

 

對於InvokeInvokeAsync僅包含一個HttpContext類型參數的狀況用到了反射(ActivatorUtilities.CreateInstance方法中)來構建RequestDelegate

var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewa
if (parameters.Length == 1)
{
    return (RequestDelegate) methodInfo.CreateDelegate(typeof(RequestDelegate), in
}

 

對於包含多個參數的狀況,則使用了表達式樹來構建RequestDelegate

var factory = Compile<object>(methodInfo, parameters);
return context =>
{
    var serviceProvider = context.RequestServices ?? applicationServices;
    if (serviceProvider == null)
    {
        throw new InvalidOperationException(
            Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(
                nameof(IServiceProvider)));
    }
    return factory(instance, context, serviceProvider);
};

 

完整的代碼能夠在Github上看到。

Use(Func<RequestDelegate, RequestDelegate> middleware)

上述全部中間件,最終都會調用IApplicationBuilder接口中的Use(Func<RequestDelegate, RequestDelegate> middleware)方法來實現向請求處理管道中註冊中間件,該方法在ApplicationBuilder類的實現以下:

public class ApplicationBuilder : IApplicationBuilder
{
    private readonly IList<Func<RequestDelegate, RequestDelegate>> _components =
        new List<Func<RequestDelegate, RequestDelegate>>();

    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        this._components.Add(middleware);
        return this;
    }
}

 

從上面代碼中能夠看到,中間件是一個RequestDelegate類型的委託,請求處理管道實際上是一個委託列表,請求委託簽名以下:

public delegate Task RequestDelegate(HttpContext context);

 


與ASP.NET處理管道的區別

 
 

 

傳統的ASP.NET的處理管道是基於事件模型的,處理管道有多個IHttpModule和一個IHttpHandler組成。請求處理管道中各個模塊被調用的順序取決於兩方面:

  • 模塊所註冊事件被觸發的前後順序
  • 註冊同一事件的不一樣模塊執行前後順序有Web.config中的配置順序決定
 

 

 

ASP.NET Core的請求處理管道則是有一堆中間件組成,相對ASP.NET更簡單。

中間件處理請求和響應的順序只與其在代碼中的註冊順序有關:處理請求按註冊順序依次執行,處理響應按註冊順序反方向依次執行。

其次,在ASP.NET Core中只需使用代碼,而無需使用Global.asaxWeb.config來配置請求處理管道。

小結

所謂中間件就是嵌入到應用管道中用於處理請求和響應的一段代碼,它主要有兩個做用:

  • 處理請求和響應
  • 能夠阻止請求發往請求處理管道中的下一個中間件

在ASP.NET Core中,中間件是以RequestDelegate委託的形式體現的。

ASP.NET Core中整個請求處理管道的建立是圍繞這種IApplicationBuilder接口進行的,請求處理管道是一個List<RequestDelegate>類型的列表。

推薦閱讀

ASP.NET Core Middleware
Factory-based middleware activation in ASP.NET Core
Migrate HTTP handlers and modules to ASP.NET Core middleware
用ASP.NET Core 2.0 創建規範的 REST API -- 預備知識

ASP.NET MVC 5 APPLICATION LIFECYCLE – HIGH-LEVEL VIEW(強烈推薦)
ASP.NET WEB API 2: HTTP MESSAGE LIFECYLE(牆裂推薦)
ASP.NET MVC5請求處理管道和生命週期

相關文章
相關標籤/搜索