asp.net core mvc 管道之中間件 http請求處理管道通過註冊中間件來實現各種功能,松耦合併且很靈活 此文簡單介紹asp.net core mvc中間件的註冊以及運行過程 通過理解中間件,將asp.net core mvc分解,以便更好地學習 中間件寫法 先看一個簡單的中間件,next ...
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