本系列將分析ASP.NET Core執行原理
- 【ASP.NET Core】執行原理[1]:建立WebHost
- 【ASP.NET Core】執行原理[2]:啟動WebHost
- 【ASP.NET Core】執行原理[3]:認證
本節將分析WebHost.StartAsync();
程式碼,確定是如何一步一步到我們註冊的中介軟體,並介紹幾種Configure的方式。
原始碼參考.NET Core 2.0.0
目錄
- Server.StartAsync
- Server
- IHttpApplication
- HttpContextFactory
- HttpContext
- Configure
- IApplicationBuilder
- Use
- Run
- UseMiddleware
- UseWhen
- MapWhen
- Map
Server.StartAsync
在上節我們知道WebHost.StartAsync
內部是呼叫Server.StartAsync
的。
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
async Task OnBind(ListenOptions endpoint)
{
var connectionHandler = new ConnectionHandler<TContext>(endpoint, ServiceContext, application);
var transport = _transportFactory.Create(endpoint, connectionHandler);
_transports.Add(transport);
await transport.BindAsync().ConfigureAwait(false);
}
await AddressBinder.BindAsync(_serverAddresses, Options.ListenOptions, Trace, OnBind).ConfigureAwait(false);
}
引數application即為之前的new HostingApplication
。在這裡說下大概的流程:
KestrelServer.StartAsync -> new ConnectionHandler<TContext>().OnConnection -> new FrameConnection().StartRequestProcessing() ->
new Frame<TContext>().ProcessRequestsAsync() -> _application.CreateContext(this) && _application.ProcessRequestAsync(context)
如果你需要更細節的流程,可參考如下:
LibuvTransportFactory -> LibuvTransport.BindAsync() -> ListenerPrimary.StartAsync() ->
listener.ListenSocket.Listen(LibuvConstants.ListenBacklog, ConnectionCallback, listener) -> listener.OnConnection(stream, status) -> ConnectionCallback() ->
new LibuvConnection(this, socket).Start() -> ConnectionHandler.OnConnection() -> connection.StartRequestProcessing() ->
ProcessRequestsAsync -> CreateFrame -> await _frame.ProcessRequestsAsync()
- _application 為上面的HostingApplication;
- 每個WebHost.StartAsync 將建立唯一的一個HostingApplication例項並在每次請求時使用。
- 由Frame類呼叫HostingApplication的方法。
下面展示Frame以及HostingApplication:
Frame
public class Frame<TContext> : Frame
{
public override async Task ProcessRequestsAsync()
{
while (!_requestProcessingStopping)
{
Reset();
EnsureHostHeaderExists();
var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
InitializeStreams(messageBody);
var context = _application.CreateContext(this);
try
{
await _application.ProcessRequestAsync(context);
}
finally
{
_application.DisposeContext(context, _applicationException);
}
}
}
}
HostingApplication
public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
private readonly RequestDelegate _application;
private readonly IHttpContextFactory _httpContextFactory;
public HostingApplication(
RequestDelegate application,
IHttpContextFactory httpContextFactory)
{
_application = application;
_httpContextFactory = httpContextFactory;
}
// Set up the request
public Context CreateContext(IFeatureCollection contextFeatures)
{
var context = new Context();
var httpContext = _httpContextFactory.Create(contextFeatures);
context.HttpContext = httpContext;
return context;
}
// Execute the request
public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext);
}
// Clean up the request
public void DisposeContext(Context context, Exception exception)
{
var httpContext = context.HttpContext;
_httpContextFactory.Dispose(httpContext);
}
public struct Context
{
public HttpContext HttpContext { get; set; }
}
}
由此我們發現HttpContext是由HttpContextFactory建立的,其中_httpContextFactory
則是上節在WebHostBuilder的BuildCommon注入的
同時在HostingApplication的ProcessRequestAsync方法中,我們看到我們的_application(Startup註冊的中介軟體)被呼叫了。
IHttpContextFactory。
HttpContextFactory
public HttpContext Create(IFeatureCollection featureCollection)
{
var httpContext = new DefaultHttpContext(featureCollection);
if (_httpContextAccessor != null)
_httpContextAccessor.HttpContext = httpContext;
return httpContext;
}
而建立的HttpContext則是DefaultHttpContext型別:
public class DefaultHttpContext : HttpContext
{
public virtual void Initialize(IFeatureCollection features)
{
_features = new FeatureReferences<FeatureInterfaces>(features);
_request = InitializeHttpRequest();
_response = InitializeHttpResponse();
}
public override HttpRequest Request => _request;
public override HttpResponse Response => _response;
}
Configure
IApplicationBuilder
我們知道在Startup的Configure方法中,透過IApplicationBuilder
可以註冊中介軟體。
public interface IApplicationBuilder
{
IServiceProvider ApplicationServices { get; set; }
RequestDelegate Build();
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}
預設實現類為:
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;
}
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
app = component(app);
return app;
}
}
其中Use方法為註冊中介軟體。中介軟體的本質就是一個Func<RequestDelegate, RequestDelegate>
物件。
該物件的傳入引數為下一個中介軟體,返回物件為本中介軟體。
而Build方法為生成一個RequestDelegate
,在HostingApplication建構函式中的引數即為該物件。
在Build方法中,我們看到最後一箇中介軟體為404中介軟體。其他的中介軟體都是透過Use方法註冊到內部維護的_components物件上。
Use
我們透過一個Use示例,來看下中介軟體的流程:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(next => async context =>
{
Console.WriteLine("A begin");
await next(context);
Console.WriteLine("A end");
});
app.Use(next => async context =>
{
Console.WriteLine("B begin");
await next(context);
Console.WriteLine("B end");
});
}
訪問結果:
A begin
B begin
B end
A end
流程圖:
Run
當我們不使用next 下一個中介軟體的時候,我們可以使用Run方法來實現
Run方法接受一個RequestDelegate物件,本身是IApplicationBuilder的擴充套件方法。
public static void Run(this IApplicationBuilder app, RequestDelegate handler);
{
app.Use(_ => handler);
}
Run示例
app.Run(context=>context.Response.WriteAsync("Run Core"));
該示例相當於:
app.Use(next => context => context.Response.WriteAsync("Run Core"));
UseMiddleware
而通常我們新增中介軟體的方式是透過UseMiddleware來更加方便的操作。
先看下IMiddleware:
public interface IMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
引數next即為下一個中介軟體。
有2種實現UseMiddleware的方式:
- 實現IMiddleware介面。
- 基於介面約定的方法。
IMiddleware介面
public class DemoMiddle : IMiddleware
{
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
return context.Response.WriteAsync("hello middleware");
}
}
在使用IMiddleware介面的時候,還需要註冊該類到DI系統中。
約定
public class DemoMiddle
{
private RequestDelegate _next;
public DemoMiddle(RequestDelegate next)
{
_next = next;
}
public Task InvokeAsync(HttpContext context)
{
return context.Response.WriteAsync("hello middleware");
}
}
這種方式,不用再註冊到DI中,如果需要對該類建構函式傳入引數,直接在app.UseMiddleware<DemoMiddle>("hi1");
傳入引數即可。
UseWhen
app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });
app.UseWhen(context => context.Request.Path.Value == "/hello", branch => branch.Use(
next => async context => { await context.Response.WriteAsync("hello"); await next(context); }));
app.Run(context => context.Response.WriteAsync("End"));
當我們訪問/hello時,結果為:BeginhelloEnd
分析原始碼得知在構建管道的時候,克隆一個另外的IApplicationBuilder。
public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
var branchBuilder = app.New();
configuration(branchBuilder);
return app.Use(main =>
{
// This is called only when the main application builder
// is built, not per request.
branchBuilder.Run(main);// 新增(呼叫)原來的中介軟體
var branch = branchBuilder.Build();
return context => predicate(context) ? branch(context): main(context);
});
}
MapWhen
app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });
app.MapWhen(context => context.Request.Path.Value == "/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));
app.Run(context => context.Response.WriteAsync("End"));
當我們訪問/hello時,結果為:Beginhello
。
分析原始碼得知在構建管道的時候,新分支並沒有再呼叫原來的中介軟體。
public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
var branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
return app.Use(next => context => predicate(context) ? branch(context): next(context));
}
Map
app.Map("/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));
當我們訪問/hello時,結果為:Beginhello
。與MapWhen效果一樣。
如果我們只是判斷URLPath的話,通常我們會使用Map方法。
以上是常用的註冊中介軟體的方式。