asp.net core mvc 管道之中介軟體
- http請求處理管道通過註冊中介軟體來實現各種功能,鬆耦合並且很靈活
- 此文簡單介紹asp.net core mvc中介軟體的註冊以及執行過程
- 通過理解中介軟體,將asp.net core mvc分解,以便更好地學習
中介軟體寫法
- 先看一個簡單的中介軟體,next是下一個委託方法,在本中介軟體的Invoke方法裡面需要執行它,否則處理就會終止,訊息處理到此中介軟體就會返回了
- 因此,根據這個約定,一箇中間生成一個委託方法,需要把所有的委託方法處理成巢狀的委託,即每個中介軟體裡面執行下一個委託,這樣處理過程就像管道一樣連線起來,每個中介軟體就是管道處理的節點
- 至於為什麼要這樣寫中介軟體,這是約定好的,還有注意點,下面將會講到
public class Middleware
{
private readonly RequestDelegate _next;
public RouterMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
// do something
await _next.Invoke(httpContext);
// do something
}
}
中介軟體管道生成
- 以上中介軟體會通過方法生成一個委託,並新增到委託集合,中間生成委託的過程後面講
namespace Microsoft.AspNetCore.Builder.Internal
{
public class ApplicationBuilder : IApplicationBuilder
{
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
}
}
- 最後的
ApplicationBuilder.Build
方法會處理所有註冊的中介軟體生成的委託集合,將所有中介軟體生成的委託集合,處理成巢狀的形式,最終得到一個委託,連成一段管道。 - 以下方法首先宣告一個響應404的委託方法,把它當成所有中介軟體的最後一個,當然它不一定會被執行到,因為某個中介軟體可能不會呼叫它
- 然後將這個委託作為引數,傳入並執行_components這個委託集合裡面的每一個委託,_components就是所有註冊的中介軟體生成的委託集合,
Reverse
方法將集合反轉,從最後註冊的中介軟體對應的委託開始處理 - 所以呢中介軟體的註冊是有順序的,也就是
Startup.cs
類裡面的Configure
方法,裡面的每個Use開頭的方法都對應一箇中介軟體註冊,程式碼的順序就是註冊的順序,也是執行的順序,千萬不能寫錯了。因為MVC處於處理流程的最後面,因此UseMvc方法總是位於最後 - 在看
component
,是從_components
委託集合裡面取出來的,執行後又得到一個RequestDelegate
型別的委託,因此由中介軟體生成的委託的型別應該是Func<RequestDelegate, RequestDelegate>
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
中介軟體生成委託
- 以下是中介軟體註冊方法,實際是呼叫
ApplicationBuilder.Use
方法,將中介軟體生成的委託加入委託集合,完成中介軟體註冊 app.Use
方法引數,就是上面需要的型別Func<RequestDelegate, RequestDelegate>
的委託,該委託的引數next
就是下一個中介軟體對應的委託,返回值就是中介軟體的Invoke
方法對應的委託,該方法用到了next
- 原始碼位於Microsoft.AspNetCore.Builder.UseMiddlewareExtensions這個類
public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
{
return app.UseMiddleware(typeof(TMiddleware), args);
}
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
// 省略部分程式碼
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
// 省略部分程式碼
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)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
}
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);
};
});
}
中介軟體寫法約定
- 看以上程式碼,第一種寫法,首先如果中間繼承自
IMiddleware
介面,則呼叫UseMiddlewareInterface
方法。使用了介面規範,那麼你也不能亂寫了,只需要注意在Invoke
方法呼叫next
即可
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 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);
}
};
});
}
- 第二種是開頭舉的例子,不繼承自介面
- 至少要有名為
Invoke
或InvokeAsync
的一個方法
public static class UseMiddlewareExtensions { internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; } 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)));
}
- 方法的引數如果只有一個,則將
UseMiddleware
方法傳入的自定義引數args
加上下一個委託next
,得到新的引數陣列,然後建立中介軟體例項,生成Invoke
方法對應委託。此處注意,如果中介軟體的建構函式中有其它引數,但是未註冊到ApplicationServices
的話,需要在UseMiddleware
方法中傳入
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)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
}
- 方法的引數如果多於一個,則呼叫
Compile
方法,生成一個委託,該委託從IServiceProvider
中獲取需要的引數的例項,再呼叫Invoke
方法,相比上面的情況,多了一步從IServiceProvider
獲取例項,注入到Invoke
而已。 Compile
方法使用了Linq表示式樹,原始碼位於Microsoft.AspNetCore.Builder.UseMiddlewareExtensions,此處不作講解,因為我也不太懂
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);
};
總結
- 以上就是通過除錯和閱讀原始碼分析得到的結果,寫出來之後閱讀可能有偏差,但這是為了方便大家理解,感覺這個順序介紹會好理解點,反正我是理解了,介紹順序對我影響不大
- 通過動手記錄的過程,把之前除錯閱讀的時候沒發現或者沒理解的點都找到弄明白了,整明白了中介軟體的註冊過程以及需要注意的書寫規範,收穫顯而易見,所以原始碼才是最好的文件,而且文件未必有這麼詳細。通過記錄,可以把細節補全甚至弄明白,這一點至關重要,再次體會到其重要性
- 另外,千萬不要在大晚上寫技術博文啊,總結之類的東西,切記
最後,文章可能有更新,請閱讀原文獲得更好的體驗哦 https://www.cnblogs.com/xxred/p/9576622.html