一、背景介绍
本文以ASP.NET Core 6以前版本API程序来说明。
在我们新建ASP.NET Core项目时,项目根目录下会自动建立Program.cs和Startup.cs两个类文件。
Program.cs 作为 Web 应用程序的默认入口,不做任何修改的情况下,会调用同目录下 Startup.cs 中的 ConfigureServices 方法 和 Configure 方法。
应用启动的流程如下图所示:
本文主要介绍Startup.cs 类,认识其功能和作用,深入了解其内部运行机制,以便于更好地在项目中进行应用。
二、program类介绍
program.Main方法是应用程序的托管入口。
在构建应用程序的主机(WebHost)时,在主程序的Web Host生成器(IWebHostBuilder)的 UseStartup <TStartup>
扩展方法中指定启动类名称。ASP.NET Core应用程序的启动类,按照惯例命名为Startup
。而Main入口通过主机生成器(IWebHostBuilder)调用Build时,生成对应的应用程序的主机(WebHost),并启动运行(Run)
Host 是一个静态类,包含了两个方法: CreateDefaultBuilder() 、CreateDefaultBuilder (args)
在下面源码中我将会剖析这两个方法的用途:
public static class Host
{
public static IhostBuilder CreateDefaultBuilder()
public static IHostBuilder CreateDefaultBuilder(string[] args);
}
源码:
public static class Host
{
public static IHostBuilder CreateDefaultBuilder() =>
CreateDefaultBuilder(args: null);
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new HostBuilder();
builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables(prefix: "DOTNET_");
if (args != null)
{
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostEnvironment env = hostingContext.HostingEnvironment;
bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
logging.AddEventLog();
}
logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
从源码可以看出:
1.首先无参的CreateDefaultBuilder()方法只是通过调用传递null值来调用有参的CreateDefaultBuilder(string[] args)方法。
2.var builder = new HostBuilder();
CreateDefaultBuilder(string[] args) 方法从创建HostBuilder 类型对象的实例化开始(继承自IHostBuilder),我们可以直接在这个实例上调用Build.Run()方法来启动我们的ASP.NET 主机,但是只是这样调用的话,并没有对我们的主机以及应用程序做配置,那么怎么配置呢?
3.Host Configuration(主机配置根目录)
builder.UseContentRoot(Directory.GetCurrentDirectory());
4.应用程序配置(Setting up the app)
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostEnvironment env = hostingContext.HostingEnvironment;
bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
5.hostingContext.HostingEnvironment表示我们在开发或生产环境信息。
bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
在程序启动配置时,它会加载我们项目中的appsettings.json及其下面的配置json 文件。通过AddJsonFile()这个方法来实现,这个方法有三个入参:
Path(string): .json 文件的相对路径位置
Optional(bool): 指定文件是否是必须的,如果设置为false,那么如果找不到文件则会抛出文件找不到异常。
ReloadOnchange(bool): 如果设置为true,那么我们改变配置文件,那么应用程序也会随之更改而无需重新启动。
bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
这段代码是.NET5 新增的,在之前.net core的应用程序中,reloadOnchange的值始终是ture,至此我们就可以对hostBuilder:reloadConfigOnChange 配置为false;
if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
这段程序的作用是如果我们需要加密我们的appsettings.json文件,则可以使用:具体使用没看。
6.配置日志(Logging)
.ConfigureLogging((hostingContext, logging) =>
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// IMPORTANT: This needs to be added *before* configuration is loaded, this lets
// the defaults be overridden by the configuration.
if (isWindows)
{
// Default the EventLogLoggerProvider to warning or above
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
// Add the EventLogLoggerProvider on windows machines
logging.AddEventLog();
}
logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
})
上述代码用来设置应用程序的生成日志;从代码中可以看出如果程序跑在windows环境下,级别是或者高于警告级别的日志将会被输出在控制台,VS输出窗口以及写入系统日志服务。具体如何操作暂时没看懂。
7.依赖注入
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
CreateDefaultBuilder 方法的最后一步是将依赖注入添加到我们的应用程序中。UseDefaultServiceProvider这个方法用来添加.NET 5 或者.NET Core自带的IOC容器。
8.对 .ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});分析
public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure);是一个静态方法返回Ihostbulider,通过CreateDefaultBuilder方法从创建然后进行进一步通过startup类进行配置组件和容器的组件。
CreateHostBuilder(args).Build().Run()
主机build源码
主机Build的过程主要完成了:
- BuildHostConfiguration:构造配置系统,初始化 IConfiguration _hostConfiguration;
- CreateHostingEnvironment:构建主机HostingEnvironment环境信息,包含 ApplicationName、 EnvironmentName、 ContentRootPath等
- CreateHostBuilderContext:创建主机Build上下文HostBuilderContext,上下文中包含: HostingEnvironment和 Configuration
- BuildAppConfiguration:构建应用程序配置
- CreateServiceProvider:创建依赖注入服务提供程序, 即依赖注入容器
三、Startup类介绍
Startup类用于配置服务和应用程序的请求管道。
因为主机启动时,执行顺序为Startup构造函数 -> ConfigureServices方法 -> Configure 方法。在Startup构造函数执行时主机只提供了这三个服务,别的服务需要在ConfigureServices方法中添加。然后到了Configure方法执行的时候,就可以使用更多的服务类型了
其中,Startup类必须定义Configure方法,但是可选择定义一个ConfigureServices 方法,这些方法将在应用程序启动时被调用。
1、ConfigureServices方法:用于设置应用程序所需要的服务。
ConfigureServices 方法在Configure方法配置应用程序服务之前被主机(WebHost)调用。其中按常规设置配置选项(appsettings.json)。
对于需要大量设置的功能,IServiceCollection 上有 Add{Service} 扩展方法,将服务添加到服务容器,使其在应用程序和Configure方法中可用。服务通过依赖关系注入(DI)或 ApplicationServices 进行解析。例如:
2、Configure方法:用于指定应用程序响应HTTP请求的方式。
可通过将中间件(middleware)组件添加到IApplicationBuilder实例来配置请求管道。Configure方法可使用 IApplicationBuilder,但未在服务容器中注册。托管创建 IApplicationBuilder并将其直接传递到Configure。
请求管道中的每个中间件组件负责调用管道中的下一个组件,或在适当情况下使链发生短路。 如果中间件链中未发生短路,则每个中间件都有第二次机会在将请求发送到客户端前处理该请求。该方法接受IApplicationBuilder作为参数,同时还可以接收其他一些可选参数,如IHostingEnvironment和ILoggerFactory。一般而言,只要将服务注册到configureServices方法中时,都可以在该方法中使用。
每个Use
扩展方法将一个中间件组件添加到请求管道。 例如,UseMvc
扩展方法将路由中间件添加到请求管道,并将MVC配置为默认处理程序。
3、扩展Startup方法:IStartupFilter
使用IStartupFilter来对Startup功能进行扩展,在应用的Configure中间件管道的开头或末尾使用IStartupFilter来配置中间件。IStartupFilter有助于确保当库在应用请求处理管道的开端或末尾添加中间件的前后运行中间件。
IStartupFilter
接口存在于Microsoft.AspNetCore.Hosting.Abstractions程序集中,它仅定义了一个接口方法。
其中Configure
方法返回了一个变量Action
。
当创建一个ASP.NET Core应用程序的时候,IApplicationBuilder
负责配置ASP.NET Core的中间件管道。例如:
在这个方法中,可以直接使用IApplicationBuilder
参数,并且可以向其中添加各种中间件。
使用IStartupFilter
, 可以指定并返回一个Action
类型的泛型委托,这意味除了可以使用方法提供的泛型委托配置IApplicationBuilder
对象, 还需要返回一个泛型委托。
IStartupFilter
方法可以接受一个配置IApplicationBuilder
的方法,换而言之IStartupFilter.Configure
方法可以使用Startup.Configure
方法作为参数。例如:
关于IStartupFilter的例子:
注册IStartupFilter
在 Startup
类的 ConfigureServices
方法中,将自定义的启动过滤器添加到依赖注入容器。
自定义的启动过滤器会在应用程序启动时自动被调用。你不需要手动调用它,它会在应用程序启动之前和之后执行你在 Configure
方法中定义的逻辑。你可以在适当的地方添加你的自定义逻辑,例如初始化设置、日志记录等。
在应用程序的启动过程中,预配置和初始化任务可以用于确保应用程序处于正确的状态并能够正常运行。以下是一些常见的预配置和初始化任务场景:
-
配置加载: 在应用程序启动时,加载应用程序的配置文件,将配置信息加载到内存中,以便后续使用。这可以包括数据库连接字符串、API 密钥、应用程序设置等。
-
数据库迁移: 如果你使用了数据库,可以在应用程序启动时执行数据库迁移,确保数据库的结构与代码的期望一致。这有助于在应用程序更新时保持数据库的同步。
-
依赖注入配置: 在应用程序启动时,配置依赖注入容器,将各个服务注册到容器中,以便在整个应用程序中使用。这可以确保在运行时能够正确解析和注入依赖项。
-
缓存预热: 预先加载某些数据到缓存中,以减少后续请求的响应时间。这可以提高应用程序的性能,尤其是对于需要频繁查询的数据。
-
日志记录配置: 配置应用程序的日志记录器,以便在应用程序启动时就能够记录日志,以及配置日志记录级别和目标(文件、数据库、远程日志服务等)。
-
资源初始化: 初始化应用程序需要的资源,比如在应用程序启动时加载某些静态文件、图像、字体等。
-
环境检查: 检查应用程序运行所需的环境是否满足要求,例如检查数据库是否可用、网络是否连接等。如果环境不满足要求,可以在启动时提前发现并报错。
-
权限验证和安全性配置: 进行身份验证和授权配置,确保只有有权限的用户能够访问应用程序的特定部分。
-
任务调度器和定时作业: 配置定时任务,以便在应用程序启动时启动定时作业,执行后台任务,如数据同步、报告生成等。
总之,预配置和初始化任务可以帮助确保应用程序在启动时处于正确的状态,以便能够正常运行和提供所需的功能。这些任务可以在应用程序启动之前或之后执行,具体取决于需求。
四、net core启动过程
一、启动执行顺序
1.ConfigureWebHostDefaults
一般是配置组件和容器的组件
2.ConfigureHostConfiguration
用来配置HostBuilder的builder过程所用到的配置的。
3.ConfigureAppConfiguration
嵌入自己的配置文件供应用程序来读取。
4.ConfigureServices
往容器里面注入应用组件
5.ConfigureLogging
注入日志组件
6.Startup
执行Startup类的构造函数
7.ConfigureServices
执行Startup类里面ConfigureServices来注入一些服务组件
8.Configure
执行Startup类里面Configure来注入中间件,处理HttpContext整个请求过程。
区别:ConfigureServices 负责将组件注册到容器中;而Configure 是配置Http处理管道的过程;