_configureServicesDelegates的承接

  在【ASP.NET Core[源码分析篇] - Startup】这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_configureServicesDelegates里面添加了注册的委托,那么系统是什么时候执行这些委托完成注册的呢?

  真正的注册 

  通过之前的一系列眼花缭乱的操作,我们得到了所有需要注册的委托_configureServicesDelegates,我们看一下WebHostBuilder.Build如何实现真正的注册。

  WebHostBuilder.Build()

  public IWebHost Build()
{
if (this._webHostBuilt)
throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
this._webHostBuilt = true;
AggregateException hostingStartupErrors;
IServiceCollection serviceCollection1 = this.BuildCommonServices(out hostingStartupErrors);
IServiceCollection serviceCollection2 = serviceCollection1.Clone();
IServiceProvider providerFromFactory = GetProviderFromFactory(serviceCollection1);
.....     WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);
try
{
webHost.Initialize();
return (IWebHost) webHost;
}
catch
{
webHost.Dispose();
throw;
} IServiceProvider GetProviderFromFactory(IServiceCollection collection)
{
ServiceProvider serviceProvider = collection.BuildServiceProvider();
IServiceProviderFactory<IServiceCollection> service = ((IServiceProvider) serviceProvider).GetService<IServiceProviderFactory<IServiceCollection>>();
if (service == null)
return (IServiceProvider) serviceProvider;
using (serviceProvider)
return service.CreateServiceProvider(service.CreateBuilder(collection));
}
}

  这里面有个最重要的方法BuildCommonServices,这个方法实现了委托的真正的执行。

private IServiceCollection BuildCommonServices(
out AggregateException hostingStartupErrors)
{
.....
     ServiceCollection services = new ServiceCollection();
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.AddOptions();
services.AddLogging();
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();     ..... foreach (Action<WebHostBuilderContext, IServiceCollection> servicesDelegate in this._configureServicesDelegates)
servicesDelegate(this._context, (IServiceCollection) services);
return (IServiceCollection) services;
}

  从上面的代码我们可以看到,首先创建了一个真正的ServiceCollection实例,然后基于这个实例添加了一些额外的重要的注册(ApplicationBuilderFactory,HttpContextFactory,DefaultServiceProviderFactory等),然后把这个ServiceCollection实例作为参数传递到_configureServicesDelegates列表的各个委托中并执行,这样的话所有在Startup需要注册的实例都已经注册在services这个ServiceCollection实例中。

  需要注意的是,到此为止程序并没有执行Startup里面的方法。

  WebHost

  当我们的BuildCommonServices完成后,返回一个ServiceCollection实例,并且基于这个ServiceCollection实例生成了一个ServiceProvider对象,然后做为生成WebHost对象的参数传递到WebHost中。

WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);

webHost.Initialize();  

  WebHost.Initialize()

  我们先看一下WebHost的Initialize方法

  public void Initialize()
{
try
{
this.EnsureApplicationServices();
}
catch (Exception ex)
{
if (this._applicationServices == null)
this._applicationServices = (IServiceProvider) this._applicationServiceCollection.BuildServiceProvider();
if (!this._options.CaptureStartupErrors)
throw;
else
this._applicationServicesException = ExceptionDispatchInfo.Capture(ex);
}
}   private void EnsureApplicationServices()
{
if (this._applicationServices != null)
return;
this.EnsureStartup();
this._applicationServices = this._startup.ConfigureServices(this._applicationServiceCollection);
} private void EnsureStartup()
{
if (this._startup != null)
return;
this._startup = this._hostingServiceProvider.GetService<IStartup>();
if (this._startup == null)
throw new InvalidOperationException(string.Format("No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {0} or specifying the startup assembly via {1} in the web host configuration.", (object) "IStartup", (object) "StartupAssemblyKey"));
}

  从上面的代码流程可以看出

  1. 解析Startup类
  2. 执行Startup类的ConfigureServices方法注册自定义的服务并返回一个IServiceProvider对象

  至此,我们的Startup类中的ConfigureServices已经执行过,并且WebHost已经具有了IServiceProvider对象  

  WebHost.Run()

  当我们调用WebHost的扩展方法Run启动应用的时候,本质上是调用了WebHost的StartAsync方法,这个过程创建了我们应用程序最为重要的用于监听、接收、处理和响应HTTP请求的管道。 

  public virtual async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken))
{
HostingEventSource.Log.HostStart();
this._logger = this._applicationServices.GetRequiredService<ILogger<WebHost>>();
this._logger.Starting();
RequestDelegate application = this.BuildApplication();
this._applicationLifetime = this._applicationServices.GetRequiredService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>() as ApplicationLifetime;
this._hostedServiceExecutor = this._applicationServices.GetRequiredService<HostedServiceExecutor>();
DiagnosticListener requiredService1 = this._applicationServices.GetRequiredService<DiagnosticListener>();
IHttpContextFactory requiredService2 = this._applicationServices.GetRequiredService<IHttpContextFactory>();
ILogger<WebHost> logger = this._logger;
DiagnosticListener diagnosticSource = requiredService1;
IHttpContextFactory httpContextFactory = requiredService2;
await this.Server.StartAsync<HostingApplication.Context>((IHttpApplication<HostingApplication.Context>) new HostingApplication(application, (ILogger) logger, diagnosticSource, httpContextFactory), cancellationToken).ConfigureAwait(false);
this._applicationLifetime?.NotifyStarted();
await this._hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
.....
}   private RequestDelegate BuildApplication()
{
this._applicationServicesException?.Throw();
this.EnsureServer();
IApplicationBuilder builder = this._applicationServices.GetRequiredService<IApplicationBuilderFactory>().CreateBuilder(this.Server.Features);
builder.ApplicationServices = this._applicationServices;
IEnumerable<IStartupFilter> service = this._applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> next = new Action<IApplicationBuilder>(this._startup.Configure);
foreach (IStartupFilter startupFilter in service.Reverse<IStartupFilter>())
next = startupFilter.Configure(next);
next(builder);
return builder.Build();
}   private void EnsureServer()
{
if (this.Server != null)
return;
this.Server = this._applicationServices.GetRequiredService<IServer>();
IServerAddressesFeature addressesFeature = this.Server.Features?.Get<IServerAddressesFeature>();
ICollection<string> addresses = addressesFeature?.Addresses;
if (addresses == null || addresses.IsReadOnly || addresses.Count != )
return;
string str1 = this._config[WebHostDefaults.ServerUrlsKey] ?? this._config[WebHost.DeprecatedServerUrlsKey];
if (string.IsNullOrEmpty(str1))
return;
addressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(this._config, WebHostDefaults.PreferHostingUrlsKey);
string str2 = str1;
char[] separator = new char[]{ ';' };
foreach (string str3 in str2.Split(separator, StringSplitOptions.RemoveEmptyEntries))
addresses.Add(str3);
}

  这块主要是Server的创建,管道的创建和监听Http请求的Server启动,我们将分步进行剖析。

  1. EnsureServer

  我们先看一下这个Server是什么

public interface IServer : IDisposable
{
IFeatureCollection Features { get; } Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken);
}

  IServer的实例其实是在开始Program里面的CreateDefaultBuilder中,已经指定了KestrelServer作为默认的Server实例。  

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
hostBuilder.UseLibuv(); return hostBuilder.ConfigureServices(services =>
{
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
services.AddSingleton<IServer, KestrelServer>();
});
}

  那么这个Server是做什么用的呢?Server 是一个HTTP服务器,负责HTTP的监听,接收一组 FeatureCollection 类型的原始请求,并将其包装成 HttpContext 以供我们的应用程序完成响应的处理。那它负责监听哪里?从代码可以看到Addresses 是通过在UseUrls里面指定的参数(WebHostDefaults.ServerUrlsKey) 或者是DeprecatedServerUrlsKey(配置文件里面的server.urls)中来查找的。

  2. BuildApplication

  在上面我们获取了一个Server用来监听请求,那么下一步我们是要构建处理Http请求的管道,IApplicationBuilder 就是用于构建应用程序的请求管道。

  我们一般的管道创建是在 Startup 类的 Configure 方法中对 IApplicationBuilder 进行配置,嗯其实在这里还有一个 IStartupFilter 也可以用来配置 IApplicationBuilder,并且在 Startup 类的Configure 方法之前执行,所有我们看到在BuildApplication方法中,一个大概的步骤是这样的:

  1. 基于IApplicationBuilderFactory创建IApplicationBuilder对象
  2. 基于IStartupFilter的管道构建
  3. 调用IApplicationBuilder对象的Build方法完成完整的管道
public RequestDelegate Build()
{
RequestDelegate requestDelegate = (RequestDelegate) (context =>
{
context.Response.StatusCode = ;
return Task.CompletedTask;
});
foreach (Func<RequestDelegate, RequestDelegate> func in this._components.Reverse<Func<RequestDelegate, RequestDelegate>>())
requestDelegate = func(requestDelegate);
return requestDelegate;
}

  3. Server.StartAsync

  在这里,Server的启动是需要一个IHttpApplication类型的参数的,来负责 HttpContext 的创建,我们看一下这个参数

public interface IHttpApplication<TContext>
{
TContext CreateContext(IFeatureCollection contextFeatures); Task ProcessRequestAsync(TContext context); void DisposeContext(TContext context, Exception exception);
}

  它的默认实现类是它的默认实现是 HostingApplication 类

public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
private readonly RequestDelegate _application;
private readonly IHttpContextFactory _httpContextFactory;public Task ProcessRequestAsync(HostingApplication.Context context)
{
return this._application(context.HttpContext);
}
  ......
}

  我们来看一下Server的Http监听绑定

public async Task StartAsync<TContext>(
IHttpApplication<TContext> application,
CancellationToken cancellationToken)
{
try
{
if (!BitConverter.IsLittleEndian)
throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
this.ValidateOptions();
if (this._hasStarted)
throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
this._hasStarted = true;
this._heartbeat.Start();
await AddressBinder.BindAsync(this._serverAddresses, this.Options, (ILogger) this.Trace, new Func<ListenOptions, Task>(OnBind)).ConfigureAwait(false);
}
catch (Exception ex)
{
this.Trace.LogCritical((EventId) , ex, "Unable to start Kestrel.");
this.Dispose();
throw;
} async Task OnBind(ListenOptions endpoint)
{
endpoint.UseHttpServer<TContext>((IList<IConnectionAdapter>) endpoint.ConnectionAdapters, this.ServiceContext, application, endpoint.Protocols);
ConnectionDelegate connectionDelegate = endpoint.Build();
if (this.Options.Limits.MaxConcurrentConnections.HasValue)
connectionDelegate = new ConnectionDelegate(new ConnectionLimitMiddleware(connectionDelegate, this.Options.Limits.MaxConcurrentConnections.Value, this.Trace).OnConnectionAsync);
ConnectionDispatcher connectionDispatcher = new ConnectionDispatcher(this.ServiceContext, connectionDelegate);
ITransport transport = this._transportFactory.Create((IEndPointInformation) endpoint, (IConnectionDispatcher) connectionDispatcher);
this._transports.Add(transport);
await transport.BindAsync().ConfigureAwait(false);
}
}

  至此为止,Server已经绑定一个监听端口,注册了HTTP连接事件,剩下的就是开启监听了。  

  4. HostedService

  HostedService 为我们开启了一个后台运行服务,它会在随着程序启动而启动。  

public class HostedServiceExecutor
{
private readonly IEnumerable<IHostedService> _services;public async Task StartAsync(CancellationToken token)
{
    await this.ExecuteAsync((Func<IHostedService, Task>) (service => service.StartAsync(token)));
} public async Task StopAsync(CancellationToken token)
{
    await this.ExecuteAsync((Func<IHostedService, Task>) (service => service.StopAsync(token)));
} private async Task ExecuteAsync(Func<IHostedService, Task> callback)
{
List<Exception> exceptions = (List<Exception>) null;
foreach (IHostedService service in this._services)
{
try
{
await callback(service);
}
catch (Exception ex)
{
if (exceptions == null)
exceptions = new List<Exception>();
exceptions.Add(ex);
}
}
if (exceptions != null)
throw new AggregateException((IEnumerable<Exception>) exceptions);
}
}

  总结

  这两篇文章从Startup开始到最后的Http管道创建和HttpServer的启动监听,涉及到了很多关键点,从代码流程来看,只要抓住几个关键点即可理解整体的一个流程。大家可以带着以下这些问题去跟着文章走:

  1. Startup有多少种实例化方式?
  2. IStartup在哪里被实例化的?
  3. IServiceCollection何时实例化的?
  4. IServiceProvider何时实例化的?
  5. Startup的ConfigureService方法何时被执行?
  6. IApplicationBuilder何时实例化的?
  7. Startup的Configure方法何时被执行?
  8. Http监听管道是何时和如何构建的?

ASP.NET Core[源码分析篇] - WebHost的更多相关文章

  1. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  2. ASP.NET Core[源码分析篇] - 认证

    追本溯源,从使用开始 首先看一下我们的通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解 public void ConfigureServ ...

  3. ASP.NET Core[源码分析篇] - Startup

    应用启动的重要类 - Startup 在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了: 配置应用需要的服务(服务 ...

  4. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  5. [asp.net core 源码分析] 01 - Session

    1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...

  6. ASP.NET Core源码学习(一)Hosting

    ASP.NET Core源码的学习,我们从Hosting开始, Hosting的GitHub地址为:https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆 ...

  7. ASP.NET MVC 源码分析(一)

    ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 ...

  8. DOTNET CORE源码分析之IOC容器结果获取内容补充

    补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...

  9. ASP.NET MVC源码分析

    MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...

随机推荐

  1. 小程序实现图片上传,预览以及图片base64位处理

    最近一段时间在做小程序项目,第一期功也完工了.需要好好总结一下经验,把项目中遇到的问题好好总结一下,遇到的问题,踩过的坑.今天写一个小程序实现图片上传,预览,以及删除,图片base64位处理.下面就是 ...

  2. 【题解】P2916 [USACO08NOV]安慰奶牛Cheering up the Cow-C++

    原题传送门 这道题用最小生成树来完成,我选用的是kruskal(克鲁斯卡尔)来完成.这道题目在克鲁斯卡尔模板的基础上,有变动的地方只有2处:1.因为必须从一个点出发,而最小生成树最后会让所有点都连通, ...

  3. 【DFS的分支限界】(例题-算式等式)

    不知道DFS的请滚去 这里瞅一眼再说. -分支限界- 基本概念: 类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法.但在一般情况下,分支限界法与回溯法的求解目标不同.回溯法的求解目标是找出T ...

  4. <a>标签 IOS 安卓 亲测有效

    一.普通链接 <a href="http://www.baidu.com">百度</a> 二.邮件链接 1.标签最简式 <a href="m ...

  5. 关于vue项目font字体图标库导入未显示的问题

    运行项目时,弹出以下信息:

  6. c++小游戏——彩票

    #include <cstdlib> #include <iostream> #include <cstdio> #include <cmath> #i ...

  7. 关于Calendar的一些用法总结

    import java.util.Calendar; import java.security.KeyStore.TrustedCertificateEntry; import java.text.D ...

  8. ehcache的使用 Shiro与Ehcache的结合(附:EhcacheUtils)

    ehcache 缓存的使用 合理的使用缓存会极大的提高程序的运行效率.切记:缓存请勿滥用. 配置ehcache与Shiro shiro初识请查看该文章 https://blog.csdn.net/py ...

  9. HttpServlet cannot be resolved to a type 解决办法

    刚开始学习Servlet,在Eclipse中新建了一个Servlet,不过页面上报错: Httpservlet cannot be resolved to a type,显然是Eclipse找不到相应 ...

  10. windows和linux下如何对拍

    对拍是各种计算机考试检查时必备工具,实际上十分强大,只要你的暴力没有写错就没有问题. 对拍的意思:(怎么有点语文课的意思雾) 对:看见'对'就可以知道有两个. 拍:就是把两个程序结果拍在一起,对照(有 ...