前言
本文編寫時源碼參考github倉庫主分支。
aspnetcore
提供了Use
方法供開發者自定義中間件,該方法接收一個委托對象,該委托接收一個RequestDelegate
對象,并返回一個RequestDelegate
對象,方法定義如下:
IApplicationBuilder?Use(Func<RequestDelegate,?RequestDelegate>?middleware);
委托RequestDelegate
的定義
///?<summary>
///?A?function?that?can?process?an?HTTP?request.
///?</summary>
///?<param?name="context">The?<see?cref="HttpContext"/>?for?the?request.</param>
///?<returns>A?task?that?represents?the?completion?of?request?processing.</returns>
public?delegate?Task?RequestDelegate(HttpContext?context);
如果我們直接使用IApplicationBuilder.Use
來寫中間件邏輯,可以使用lamda表達式來簡化代碼,如下:
app.Use((RequestDelegate?next)?=>
{return?(HttpContext?ctx)?=>{//?do?your?logicreturn?next(ctx);};
});
如果寫一些簡單的邏輯,這種方式最為方便,問題是如果需要寫的中間件代碼比較多,依然這樣去寫,會導致我們Program.cs
文件代碼非常多,如果有多個中間件,那么最后我們的的Program.cs
文件包含多個中間件代碼,看上去十分混亂。
將中間件邏輯獨立出來
為了解決我們上面的代碼不優雅,我們希望能將每個中間件業務獨立成一個文件,多個中間件代碼不混亂的搞到一起。我們需要這樣做。
單獨的中間件文件
// Middleware1.cs
public?class?Middleware1
{public?static?RequestDelegate?Logic(RequestDelegate?requestDelegate){return?(HttpContext?ctx)?=>{//?do?your?logicreturn?requestDelegate(ctx);};}
}
調用中間件
app.Use(Middleware1.Logic);
// 以下是其他中間件示例
app.Use(Middleware2.Logic);
app.Use(Middleware3.Logic);
app.Use(Middleware4.Logic);
這種方式可以很好的將各個中間件邏輯獨立出來,Program.cs
此時變得十分簡潔,然而我們還不滿足這樣,因為我們的Logic
方法中直接返回一個lamada表達式(RequestDelegate
對象),代碼層級深了一層,每個中間件都多寫這一層殼似乎不太優雅,能不能去掉這層lamada表達式呢?
UseMiddlewareExtensions
為了解決上面提到的痛點,UseMiddlewareExtensions
擴展類應運而生,它在Aspnetcore
底層大量使用,它主要提供一個泛型UseMiddleware<T>
方法用來方便我們注冊中間件,下面是該方法的定義
public?static?IApplicationBuilder?UseMiddleware<TMiddleware>(this?IApplicationBuilder?app,?params?object?[]?args)
如果只看這個方法的聲明,估計沒人知道如何使用,因為該方法接收的泛型參數TMiddleware
沒有添加任何限制,而另一個args
參數也是object
類型,而且是可以不傳的,也就是它只需要傳任意一個類型都不會在編譯時報錯。 比如這樣,完全不會報錯:
當然,如果你這樣就運行程序,一定會收到下面的異常
System.InvalidOperationException:“No public 'Invoke' or 'InvokeAsync' method found for middleware of type 'System.String'.”
提示我們傳的類型沒有Invoke
或InvokeAsync
公共方法,這里大概能猜到,底層應該是通過反射進行動態調用Invoke
或InvokeAsync
公共方法的。
源碼分析
想要知道其本質,唯有查看源碼,以下源碼來自UseMiddlewareExtensions
如下,該擴展類一共提供兩個并且是重載的公共方法UseMiddleware
,一般都只會使用第一個UseMiddleware
,第一個UseMiddleware
方法內部再去調用第二個UseMiddleware
方法,源碼中對類型前面添加的[DynamicallyAccessedMembers(MiddlewareAccessibility)]
屬性可以忽略,它的作用是為了告訴編譯器我們通過反射訪問的范圍,以防止對程序集對我們可能調用的方法或屬性等進行裁剪。
internal?const?string?InvokeMethodName?=?"Invoke";
internal?const?string?InvokeAsyncMethodName?=?"InvokeAsync";///?<summary>
///?Adds?a?middleware?type?to?the?application's?request?pipeline.
///?</summary>
///?<typeparam?name="TMiddleware">The?middleware?type.</typeparam>
///?<param?name="app">The?<see?cref="IApplicationBuilder"/>?instance.</param>
///?<param?name="args">The?arguments?to?pass?to?the?middleware?type?instance's?constructor.</param>
///?<returns>The?<see?cref="IApplicationBuilder"/>?instance.</returns>
public?static?IApplicationBuilder?UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]?TMiddleware>(this?IApplicationBuilder?app,?params?object?[]?args)
{return?app.UseMiddleware(typeof(TMiddleware),?args);
}///?<summary>
///?Adds?a?middleware?type?to?the?application's?request?pipeline.
///?</summary>
///?<param?name="app">The?<see?cref="IApplicationBuilder"/>?instance.</param>
///?<param?name="middleware">The?middleware?type.</param>
///?<param?name="args">The?arguments?to?pass?to?the?middleware?type?instance's?constructor.</param>
///?<returns>The?<see?cref="IApplicationBuilder"/>?instance.</returns>
public?static?IApplicationBuilder?UseMiddleware(this?IApplicationBuilder?app,[DynamicallyAccessedMembers(MiddlewareAccessibility)]?Type?middleware,params?object?[]?args)
{if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}var?applicationServices?=?app.ApplicationServices;var?methods?=?middleware.GetMethods(BindingFlags.Instance?|?BindingFlags.Public);MethodInfo??invokeMethod?=?null;foreach?(var?method?in?methods){if?(string.Equals(method.Name,?InvokeMethodName,?StringComparison.Ordinal)?||?string.Equals(method.Name,?InvokeAsyncMethodName,?StringComparison.Ordinal)){if?(invokeMethod?is?not?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName,?InvokeAsyncMethodName));}invokeMethod?=?method;}}if?(invokeMethod?is?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName,?InvokeAsyncMethodName,?middleware));}if?(!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType)){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName,?InvokeAsyncMethodName,?nameof(Task)));}var?parameters?=?invokeMethod.GetParameters();if?(parameters.Length?==?0?||?parameters[0].ParameterType?!=?typeof(HttpContext)){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,?InvokeAsyncMethodName,?nameof(HttpContext)));}var?state?=?new?InvokeMiddlewareState(middleware);return?app.Use(next?=>{var?middleware?=?state.Middleware;var?ctorArgs?=?new?object[args.Length?+?1];ctorArgs[0]?=?next;Array.Copy(args,?0,?ctorArgs,?1,?args.Length);var?instance?=?ActivatorUtilities.CreateInstance(app.ApplicationServices,?middleware,?ctorArgs);if?(parameters.Length?==?1){return?(RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate),?instance);}var?factory?=?Compile<object>(invokeMethod,?parameters);return?context?=>{var?serviceProvider?=?context.RequestServices????applicationServices;if?(serviceProvider?==?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}return?factory(instance,?context,?serviceProvider);};});
}
第一個UseMiddleware
可以直接跳過,看第二個UseMiddleware
方法,該方法一上來就先判斷我們傳的泛型類型是不是IMiddleware
接口的派生類,如果是,直接交給UseMiddlewareInterface
方法。
if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}
這里總算看到應該有的東西了,如果聲明UseMiddleware<T>
方法時,對泛型T
添加IMiddleware
限制,我們不看源碼就知道如何編寫我們的中間件邏輯了,只需要寫一個類,繼承IMiddleware
并實現InvokeAsync
方法即可, UseMiddlewareInterface
方法的實現比較簡單,因為我們繼承了接口,邏輯相對會簡單點。
private?static?IApplicationBuilder?UseMiddlewareInterface(IApplicationBuilder?app,Type?middlewareType)
{return?app.Use(next?=>{return?async?context?=>{var?middlewareFactory?=?(IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));if?(middlewareFactory?==?null){//?No?middleware?factorythrow?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}var?middleware?=?middlewareFactory.Create(middlewareType);if?(middleware?==?null){//?The?factory?returned?null,?it's?a?broken?implementationthrow?new?InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(),?middlewareType));}try{await?middleware.InvokeAsync(context,?next);}finally{middlewareFactory.Release(middleware);}};});
}
public?interface?IMiddleware
{///?<summary>///?Request?handling?method.///?</summary>///?<param?name="context">The?<see?cref="HttpContext"/>?for?the?current?request.</param>///?<param?name="next">The?delegate?representing?the?remaining?middleware?in?the?request?pipeline.</param>///?<returns>A?<see?cref="Task"/>?that?represents?the?execution?of?this?middleware.</returns>Task?InvokeAsync(HttpContext?context,?RequestDelegate?next);
}
如果我們的類不滿足IMiddleware
,繼續往下看
通過反射查找泛型類中Invoke
或InvokeAsync
方法
var?applicationServices?=?app.ApplicationServices;
var?methods?=?middleware.GetMethods(BindingFlags.Instance?|?BindingFlags.Public);
MethodInfo??invokeMethod?=?null;
foreach?(var?method?in?methods)
{if?(string.Equals(method.Name,?InvokeMethodName,?StringComparison.Ordinal)?||?string.Equals(method.Name,?InvokeAsyncMethodName,?StringComparison.Ordinal)){// 如果Invoke和InvokeAsync同時存在,則拋出異常,也就是,我們只能二選一if?(invokeMethod?is?not?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName,?InvokeAsyncMethodName));}invokeMethod?=?method;}
}// 如果找不到Invoke和InvokeAsync則拋出異常,上文提到的那個異常。
if?(invokeMethod?is?null)
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName,?InvokeAsyncMethodName,?middleware));
}// 如果Invoke和InvokeAsync方法的返回值不是Task或Task的派生類,則拋出異常
if?(!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName,?InvokeAsyncMethodName,?nameof(Task)));
}
Snippet// 如果Invoke和InvokeAsync方法沒有參數,或第一個參數不是HttpContext,拋異常
var?parameters?=?invokeMethod.GetParameters();
if?(parameters.Length?==?0?||?parameters[0].ParameterType?!=?typeof(HttpContext))
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,?InvokeAsyncMethodName,?nameof(HttpContext)));
}
上面一堆邏輯主要就是檢查我們的Invoke
和InvokeAsync
方法是否符合要求,即:必須是接收HttpContext
參數,返回Task
對象,這恰好就是委托RequestDelegate
的定義。
構造RequestDelegate這部分源碼的解讀都注釋到相應的位置了,如下
var?state?=?new?InvokeMiddlewareState(middleware);
// 調用Use函數,向管道中注冊中間件
return?app.Use(next?=>
{var?middleware?=?state.Middleware;var?ctorArgs?=?new?object[args.Length?+?1];// next是RequestDelegate對象,作為構造函數的第一個參數傳入ctorArgs[0]?=?next;Array.Copy(args,?0,?ctorArgs,?1,?args.Length);// 反射實例化我們傳入的泛型類,并把next和args作為構造函數的參數傳入var?instance?=?ActivatorUtilities.CreateInstance(app.ApplicationServices,?middleware,?ctorArgs);// 如果我們的Invoke方法只有一個參數,則直接創建該方法的委托if?(parameters.Length?==?1){return?(RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate),?instance);}// 當Invoke方法不止一個參數HttpContext,通過Compile函數創建動態表達式目錄樹,// 表達式目錄樹的構造此處略過,其目的是實現將除第一個參數的其他參數通過IOC注入var?factory?=?Compile<object>(invokeMethod,?parameters);return?context?=>{// 獲取serviceProvider用于在上面構造的表達式目錄樹中實現依賴注入var?serviceProvider?=?context.RequestServices????applicationServices;if?(serviceProvider?==?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}// 將所需的參數傳入構造的表達式目錄樹工廠return?factory(instance,?context,?serviceProvider);};
});
至此,整個擴展類的源碼就解讀完了。
通過UseMiddleware注入自定義中間件
通過上面的源碼解讀,我們知道了其實我們傳入的泛型類型是有嚴格的要求的,主要有兩種
通過繼承IMiddleware
繼承IMiddleware
并實現該接口的InvokeAsync
函數
public?class?Middleware1?:?IMiddleware
{public?async?Task?InvokeAsync(HttpContext?context,?RequestDelegate?next){//?do?your?logicawait?next(context);}
}
通過反射
我們知道,在不繼承IMiddleware
的情況下,底層會通過反射實例化泛型類型,并通過構造函數傳入RequestDelegate
,而且要有一個公共函數Invoke
或InvokeAsync
,并且接收的第一個參數是HttpContext
,返回Task
,根據要求我們將Middleware1.cs
改造如下
public?class?Middleware1
{RequestDelegate?next;public?Middleware1(RequestDelegate?next){this.next?=?next;}public?async?Task?Invoke(HttpContext?httpContext){//?do?your?logicawait?this.next(httpContext);}
}
總結
通過源碼的學習,我們弄清楚底層注冊中間件的來龍去脈,兩種方式根據自己習慣進行使用,筆者認為通過接口的方式更加簡潔直觀簡單,并且省去了反射帶來的性能損失,推薦使用。既然通過繼承接口那么爽,為啥還費那么大勁實現反射的方式呢?由源碼可知,如果繼承接口的話,就不能進行動態傳參了。
if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}
所以在需要傳參的場景,則必須使用反射的方式,所以兩種方式都有其存在的必要。
如果本文對您有幫助,還請點贊轉發關注一波支持作者。