一个典型的ASP.NET Core应用程序会包含Program与Startup两个文件。Program类中有应用程序的入口方法Main,其中的处理逻辑通常是创建一个WebHostBuilder,再生成WebHost,最后启动之。

而在创建WebHostBuilder时又会常常会指定一个Startup类型。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();

这个Startup类里究竟做了哪些事情呢?

查一下UseStartup方法的实现内容:

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.ConfigureServices(services =>
{
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup), startupType);
}
else
{
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});
}
});
}

可以看出所指定的Startup类型会在DI容器中注册为单例形式,注册的处理过程被封装成Action类型,并被加入WebHostBuilder类中的相关集合中。

同时Startup类型可以有两种实现方式:

  • 自定义实现IStartup接口的类
  • 内部定义的ConventionBasedStartup类

实际使用的Startup类经常是这样的:

public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services)
{
...
} public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
}
}

所以明显不是第一种方式,那么其又是怎么与第二种方式关联起来的?

ConventionBasedStartup的构造方法中传入了StartupMethods类型的参数,它的LoadMethod方法里曝露了更多的信息。

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
var configureMethod = FindConfigureDelegate(startupType, environmentName); var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); object instance = null;
if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
} // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not
// going to be used for anything.
var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
hostingServiceProvider,
servicesMethod,
configureContainerMethod,
instance); return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
}

它的内部处理中会找寻三种方法,ConfigureServices, Configure与ConfigureContainer。

private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod);
} private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
return new ConfigureContainerBuilder(configureMethod);
} private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
{
var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
return new ConfigureServicesBuilder(servicesMethod);
}

ConfigureServices方法用于在容器中注册各种所需使用的服务接口类型,Configure方法中可以使用各种middleware(中间件),对HTTP请求pipeline(管道)进行配置。

这二者与IStartup接口的方法基本一致,而ConfigureContainer方法则是提供了一种引入第三方DI容器的功能。

public interface IStartup
{
IServiceProvider ConfigureServices(IServiceCollection services); void Configure(IApplicationBuilder app);
}

使用第二种Startup类型的实现方式时,对于Configure方法的参数也可以更加灵活,不仅可以传入IApplicationBuilder类型,还可以有其它已注册过的任意接口类型。

ConfigureBuilder类的Build方法对额外参数的处理方法简单明了,是IApplicationBuilder类型的直接传入参数实例,不是的则从DI容器中获取实例:

public class ConfigureBuilder
{
public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder); private void Invoke(object instance, IApplicationBuilder builder)
{
// Create a scope for Configure, this allows creating scoped dependencies
// without the hassle of manually creating a scope.
using (var scope = builder.ApplicationServices.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var parameterInfos = MethodInfo.GetParameters();
var parameters = new object[parameterInfos.Length];
for (var index = 0; index < parameterInfos.Length; index++)
{
var parameterInfo = parameterInfos[index];
if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
{
parameters[index] = builder;
}
else
{
try
{
parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
}
...
}
}
MethodInfo.Invoke(instance, parameters);
}
}
}

此外,在找寻各种方法的处理中可以看到环境变量的身影。所以用第二种方式可以依据不同的环境变量定义同类型但不同名称的方法,这样可以省去写不少if...else...的处理。

UseStartup方法中还只是申明了需要注册Startup类型,实际的调用是在WebHostBuilder类执行Build方法时发生的。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
... foreach (var configureServices in _configureServicesDelegates)
{
configureServices(_context, services);
} return services;
}

至于Startup类型中方法的调用时机,则需跟踪到WebHost类中。

首先是它的Initialize方法会确保获得Startup类的实例,并调用ConfigureServices方法注册服务接口。

private void EnsureApplicationServices()
{
if (_applicationServices == null)
{
EnsureStartup();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
}
} private void EnsureStartup()
{
if (_startup != null)
{
return;
} _startup = _hostingServiceProvider.GetService<IStartup>(); ...
}

然后WebHost实例被启动时,它的BuildApplication方法会创建一个ApplicationBuilder实例,以其作为Configure方法参数,同时调用Configure方法,这时Configure方法中对那些middleware的处理方式(即Func<RequestDelegate, RequestDelegate>方法)会被加入到ApplicationBuilder之中。

private RequestDelegate BuildApplication()
{
try
{
_applicationServicesException?.Throw();
EnsureServer(); var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
} configure(builder); return builder.Build();
}
...
}

ApplicationBuilder的Build方法将这些处理逻辑嵌套起来,最底层的是返回404未找到的处理逻辑。

public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
}; foreach (var component in _components.Reverse())
{
app = component(app);
} return app;
}

BuildApplication方法返回已嵌套的RequestDelegate委托方法,并在之后生成的HostingApplication实例中,将其传入它的构造方法。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
HostingEventSource.Log.HostStart();
_logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
_logger.Starting(); var application = BuildApplication(); _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); ...
}

上述方法中HostingApplication实例最终被传入KestrelServer的启动方法中,这样才能在其内部调用HostingApplication的ProcessRequestAsync方法,并开始层层调用诸多的RequestDelegate方法。

public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext);
}

如果觉得使用Startup类还是有点麻烦的话,直接使用WebHostBuilder所提供的扩展方法也是同样的效果。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
HostingEnvironment = hostingContext.HostingEnvironment;
Configuration = config.Build();
})
.ConfigureServices(services =>
{
services.AddMvc();
})
.Configure(app =>
{
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
} app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
});

不过使用独立的Startup类有着额外的好处,Startup类可以被包含在与Program类不用的的程序集中。然后通过WebHostOptions类中StartupAssembly属性的设定及其它相关处理,完成UseStartup方法同样的功能。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
... if (!string.IsNullOrEmpty(_options.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup), startupType);
}
else
{
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
return new ConventionBasedStartup(methods);
});
}
}
...
} ... return services;
}

.NET Core开发日志——Startup的更多相关文章

  1. .NET Core开发日志——RequestDelegate

    本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...

  2. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  3. .NET Core开发日志——Entity Framework与PostgreSQL

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  4. .NET Core开发日志——从搭建开发环境开始

    .NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...

  5. .NET Core开发日志——Peachpie

    .NET Core的生态圈随着开源社区的力量不断注入至其中,正在变得越来越强盛,并且不时得就出现些有意思的项目,比如Peachpie,它使得PHP的代码迁移到.NET Core项目变得可能. 从创建简 ...

  6. .NET Core开发日志——GraphQL

    GraphQL是什么 GraphQL既是一种用于API的查询语言也是一种通过使用对应数据的类型系统,执行数据查询的服务端运行时.GraphQL没有局限于任何数据库或存储引擎,而是通过既有代码及数据获得 ...

  7. .NET Core开发日志——OData

    简述 OData,即Open Data Protocol,是由微软在2007年推出的一款开放协议,旨在通过简单.标准的方式创建和使用查询式及交互式RESTful API. 类库 在.NET Core中 ...

  8. .NET Core开发日志——结构化日志

    在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...

  9. .NET Core开发日志——Edge.js

    最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...

随机推荐

  1. JVM的7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理

    原文地址:https://blog.csdn.net/tjiyu/article/details/53983650 下面先来了解HotSpot虚拟机中的7种垃圾收集器:Serial.ParNew.Pa ...

  2. +: indexed part-select

    That syntax is called an indexed part-select. The first term is the bit offset and the second term i ...

  3. JAVA 线程池架构浅析

    经历了Java内存模型.JUC基础之AQS.CAS.Lock.并发工具类.并发容器.阻塞队列.atomic类后,我们开始JUC的最后一部分:线程池.在这个部分你将了解到下面几个部分: 线程池的基础架构 ...

  4. cad巧用插件自定义填充图形

    很多同志如果遇到奇葩的填充图案,怎么办,找不到合适的,自己辛苦画了一遍,想把它作为自己的自定义的图案,怎么办呢. 今天老王给你您介绍个好用的插件. 首先在命令行输入命令  ap 弹出加载对话框 打开窗 ...

  5. 配合angularjs中interceptor一劳永逸的加载$ionicloading的方法

    在我们日常的项目开发中,每当页面需要和服务端存在交互的时候,为了界面的友好,我们都会在界面中给个loading的加载图标,当从服务端获取到数据或者已经把本地数据送到服务端并且得到相应的回应的时候我们就 ...

  6. 教你怎么上传本地代码到github

    第一步:建立git仓库 cd到你的本地项目根目录下,执行git命令 git init 第二步:去github上创建自己的Repository,创建完成后拿到创建的仓库的https地址 第三步:将本地的 ...

  7. hdoj:2035

    #include <iostream> using namespace std; int main() { long a, b; && b != ) { long resu ...

  8. Angularjs的那些事 – 视图的生命周期

    Angularjs的最主要的一个应用场景就是单页面应用(SinglePageApplication),但是SPA中会有一个明显的问题,在视图切换的时候,它只会用新视图去替换视图容器内的HTML,但如果 ...

  9. 关于Unity的两种调试方法

    Unity的两种调试方法 1.Debug.Log()输出语句调试,平时经常用这个 2.把MonoDevelop和Unity进行连接后断点调试 先把编辑器选择为MonoDevelop,Edit----& ...

  10. [Installing Metasploit Framework on CentOS_RHEL 6]在CentOS_RHEL 6上安装Metasploit的框架【翻译】

    [Installing Metasploit Framework on CentOS_RHEL 6]在CentOS_RHEL 6上安装Metasploit的框架[翻译] 标记声明:蓝色汉子为翻译上段英 ...