在啓動流程文章中提到,在WebHost類中,通過BuildApplication完成http請求處理管道的構建。在來看一下代碼:
。。。。。。
//這個調用的就是Startup.cs類中的Configure方法
configure(builder);
//生成中間件鏈式結構
return builder.Build();
在框架中,一箇中間件處理邏輯是使用一個RequestDelegate委託類型來表示的,定義:delegate Task RequestDelegate(HttpContext context)
那是不是我們直接創建委託方法就可以了?答案是否定的,爲了形成一個鏈式結構,中間定義跟註冊都有一定的要求。
首先先介紹下如何定義一箇中間件。定義中間件只需要定義一個類即可,但是這個類並不是隨意寫,裏面的結構由一定的要求:
1,類必須包含一個構造方法,這個構造方法的第一個參數必須是一個RequestDelegate類型,這個參數表達的就是當前定義中間件的下一個中間件。
2,必須包含一個Invoke方法,方法的第一個參數必須是HttpContext類型,返回值類型必須是Task,Invoke方法中實現中間件邏輯
下面是一箇中間件定義實例代碼:
class MiddlewareSample
{
private RequestDelegate _next;
public MiddlewareSample(RequesetDelegate next)
{
_next=next;
}
public Task Invoke(HttpContext context)
{
//中間件邏輯代碼
......
//調用下一個中間件,實現鏈式調用,當然可以根據業務場景去確定是否繼續往下調用
_next(context);
}
}
再來看下,如何把定義好的中間件註冊到管道中。IApplicationBuilder提供了UseMiddleware的擴展方法,通過這個方法就可以把一箇中間件類型註冊到管道中。那我們來看一下,它裏面到底做了什麼?
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
//通過反射獲取Invoke方法信息
var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray();
if (invokeMethods.Length > 1)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
}
if (invokeMethods.Length == 0)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
}
var methodinfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, nameof(Task)));
}
var parameters = methodinfo.GetParameters();
//判斷Invoke方法是否包含HttpContext參數,並且要求是第一個參數
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext)));
}
var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
//實例化中間件類型,並把next最爲構造方法的第一個參數傳遞進去
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
if (parameters.Length == 1)
{
//如果invoke方法只包含一個httpcontext參數,直接創建RequestDelegate
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
}
//這裏我把它理解成一個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);
};
});
}
在UseMiddleware方法中,其實是通過反射的方式,解析中間件定義的類型,最後創建了一箇中間件工廠委託對象,工廠就是一個Func<RequestDelegate, RequestDelegate>委託類型,它接收一個RequestDelegate中間件,然後返回一個新的中間件,接收的這個中間件參數是新生成的這個中間件的下一個中間件。創建好工廠後,調用IApplicationBuilder.Use方法,把這個工廠加入到中間件工廠列表中,Use方法實現如下:
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
到此只是有了一箇中間件工廠集合,最後通過調用builder.Build()方法中完成的中間件鏈式結構的生成。
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return TaskCache.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
首先定義了一個返回404錯誤異常的一個RequestDelegate,這個是作爲中間件的然後把中間件集合反轉,依次調用中間件工廠來完成中間件的初始化。這裏爲什麼要反轉,我們來分析下。
假如我們註冊中間件順序爲1->2->3->4,那_components中工廠的順序就是工廠1->工廠2->工廠3->工廠4,反轉後就成了工廠4->工廠3->工廠2->工廠1,然後進入for循環,首先調用工廠4,用返回404錯誤中間件作爲參數,最後返回一箇中間件4,然後調用工廠3,把中間件4傳遞進去,再生成一箇中間件,最後調用工廠1,這樣就形成了 中間件1->中間件2->中間件3->中間件4->404錯誤中間件 這樣的鏈式結構。當一個http請求過來後,就會按照這個順序依次執行對應的中間件邏輯。