本文主要是對 ".NET Core開發日誌——Middleware" 的補遺,但是會從看起來平平無奇的RequestDelegate開始敘述,所以以其作為標題,也是合情合理。 RequestDelegate是一種委托類型,其全貌為 ,MSDN上對它的解釋,"A function that can p ...
本文主要是對.NET Core開發日誌——Middleware的補遺,但是會從看起來平平無奇的RequestDelegate開始敘述,所以以其作為標題,也是合情合理。
RequestDelegate是一種委托類型,其全貌為public delegate Task RequestDelegate(HttpContext context)
,MSDN上對它的解釋,"A function that can process an HTTP request."——處理HTTP請求的函數。唯一參數,是最熟悉不過的HttpContext,返回值則是表示請求處理完成的非同步操作類型。
可以將其理解為ASP.NET Core中對一切HTTP請求處理的抽象(委托類型本身可視為函數模板,其實現具有統一的參數列表及返回值類型),沒有它整個框架就失去了對HTTP請求的處理能力。
並且它也是構成Middleware的基石。或者更準確地說參數與返回值都是其的Func<RequestDelegate, RequestDelegate>
委托類型正是維持Middleware運轉的核心齒輪。
組裝齒輪的地方位於ApplicationBuilder類之內,其中包含著所有齒輪的集合。
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
以及添加齒輪的方法:
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
在Startup類的Configure方法里調用以上ApplicationBuilder的Use方法,就可以完成一個最簡單的Middleware。
public void Configure(IApplicationBuilder app)
{
app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
};
});
}
齒輪要想變成Middleware,在完成添加後,還需要經過組裝。
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
Build方法里先定義了最底層的零件——app,context => { context.Response.StatusCode = 404; return Task.CompletedTask; }
,這段代碼意味著,如果沒有添加任何Middleware的話,ASP.NET Core站點啟動後,會直接出現404的錯誤。
接下的一段,遍歷倒序排列的齒輪,開始正式組裝。
在上述例子里,只使用了一個齒輪:
_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
};
}
那麼第一次也是最後一次迴圈後,執行component(app)
操作,app被重新賦值為:
context => context.Response.WriteAsync("Hello, World!");
組裝的結果便是app的值。
這個組裝過程在WebHost進行BuildApplication時開始操作。從此方法的返回值類型可以看出,雖然明義上是創建Application,其實生成的是RequestDelegate。
private RequestDelegate BuildApplication()
{
try
{
...
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
...
Action<IApplicationBuilder> configure = _startup.Configure;
...
configure(builder);
return builder.Build();
}
...
}
而這個RequestDelegate最終會在HostingApplication類的ProcessRequestAsync方法里被調用。
public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
...
var application = BuildApplication();
...
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
...
}
public HostingApplication(
RequestDelegate application,
ILogger logger,
DiagnosticListener diagnosticSource,
IHttpContextFactory httpContextFactory)
{
_application = application;
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
_httpContextFactory = httpContextFactory;
}
public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext);
}
上例中的執行結果即是顯示Hello, World!字元。
404的錯誤不再出現,意味著這種Middleware只會完成自己對HTTP請求的處理,並不會將請求傳至下一層的Middleware。
要想達成不斷傳遞請求的目的,需要使用另一種Use擴展方法。
public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{
return app.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}
在實際代碼中可以這麼寫:
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
});
app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
};
});
}
現在多了個Middleware,繼續上面的組裝過程。app的值最終被賦值為:
async context =>
{
Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!");
await context.Response.WriteAsync("I am a Middleware!\n");
await simpleNext.Invoke();
};
顯示結果為:
I am a Middleware!
Hello, World!
下麵的流程圖中可以清楚地說明這個過程。
如果把await next.Invoke()
註釋掉的話,
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
//await next.Invoke();
});
app.Use(_ =>
{
return context =>
{
return context.Response.WriteAsync("Hello, World!");
};
});
}
上例中第一個Middleware處理完後,不會繼續交給第二個Middleware處理。註意以下simpleNext的方法只被定義而沒有被調用。
async context =>
{
Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!");
await context.Response.WriteAsync("I am a Middleware!\n");
};
這種情況被稱為短路(short-circuiting)。
做短路處理的Middleware一般會放在所有Middleware的最後,以作為整個pipeline的終點。
並且更常見的方式是用Run擴展方法。
public static void Run(this IApplicationBuilder app, RequestDelegate handler)
{
...
app.Use(_ => handler);
}
所以可以把上面例子的代碼改成下麵的形式:
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
除了短路之外,Middleware處理時還可以有分支的情況。
public void Configure(IApplicationBuilder app)
{
app.Map("/branch1", ab => {
ab.Run(async context =>
{
await context.Response.WriteAsync("Map branch 1");
});
});
app.Map("/branch2", ab => {
ab.Run(async context =>
{
await context.Response.WriteAsync("Map branch 2");
});
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("I am a Middleware!\n");
await next.Invoke();
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
URL地址後面跟著branch1時:
URL地址後面跟著branch2時:
其它情況下:
Map擴展方法的代碼實現:
public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
{
...
// create branch
var branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapOptions
{
Branch = branch,
PathMatch = pathMatch,
};
return app.Use(next => new MapMiddleware(next, options).Invoke);
}
創建分支的辦法就是重新實例化一個ApplicationBuilder。
public IApplicationBuilder New()
{
return new ApplicationBuilder(this);
}
對分支的處理則是封裝在MapMiddleware類之中。
public async Task Invoke(HttpContext context)
{
...
PathString matchedPath;
PathString remainingPath;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
{
// Update the path
var path = context.Request.Path;
var pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matchedPath);
context.Request.Path = remainingPath;
try
{
await _options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
}
else
{
await _next(context);
}
}
說到MapMiddleware,不得不提及各種以Use開頭的擴展方法,比如UseStaticFiles,UseMvc,UsePathBase等等。
這些方法內部都會調用UseMiddleware方法以使用各類定製的Middleware類。如下麵UsePathBase的代碼:
public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase)
{
...
// Strip trailing slashes
pathBase = pathBase.Value?.TrimEnd('/');
if (!pathBase.HasValue)
{
return app;
}
return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
}
而從UseMiddleware方法中可以獲知,Middleware類需滿足兩者條件之一才能被有效使用。其一是實現IMiddleware,其二,必須有Invoke或者InvokeAsync方法,且方法至少要有一個HttpContext類型參數(它還只能是放第一個),同時返回值需要是Task類型。
internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
{
...
return UseMiddlewareInterface(app, middleware);
}
var applicationServices = app.ApplicationServices;
return app.Use(next =>
{
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();
...
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;
...
return factory(instance, context, serviceProvider);
};
});
}
對ASP.NET Core中Middleware的介紹到此終於可以告一段落,希望這兩篇文章能夠為讀者提供些許助力。