ASP.NET Core Middleware


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

Conventional Middleware

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

匿名方法

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

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)方法來實現向請求處理管道中注入中間件,后面會對源碼做分析。

自定義中間件類

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

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

 

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

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

IMiddleware

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

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

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

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請求處理管道和生命周期


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM