.NET Core技术研究-主机
前一段时间,和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程
但是没有深入说明主机的设计。今天整理了一下主机的一些知识,结合先前的博文,完整地介绍一下.NET Core的主机的设计和构建启动过程。
一、什么是主机
主机是一个封装了应用资源的对象,即:主机封装了一堆应用资源,封装了哪些应用资源呢?
- 依赖注入框架 DI
- Logging日志
- Configuration 配置
- 托管服务:IHostedService服务接口的实现
二、Web主机和通用主机
先说Web主机:即ASP.NET Core Web主机,概括的讲就是托管Web程序的Host。在低于 3.0 的 ASP.NET Core 版本中,Web 主机用于 HTTP 工作负载。
我们新建一个ASP.NET Core2.2的Web应用程序,在Program类的Main函数中我们可以看到整个WebHost的构造、启动过程:
.NET Core提供Web主机的同时,还提供了一个通用主机的概念。
通用主机Host和Web主机提供了类似的架构和功能,包含依赖注入框架DI、日志、配置、各类应用(托管服务)。通用主机的出现,给了我们更多开发的选择,比如说后台处理任务场景。
在.NET Core3.1版本后,微软不再建议将 Web 主机用于 Web 应用,直接使用Host通用主机来替换WebHost,
一句话:通用主机可以托管任何类型的应用,包括 Web 应用。 通用主机将替换 Web 主机。为了向下兼容,WebHost依然可以使用。
我们新建一个ASP.NET Core3.1的Web应用程序,在Program类的Main函数中我们可以看到整个WebHost的构造、启动过程:
接下来,我们将以ASP.NET Core 3.1这个版本,介绍一下主机的构建过程和启动过程
三、主机是如何构建的
从上述代码可以看到,Main函数中首先调用CreateHostBuilder方法,返回一个IHostBuilder。然后调用IHostBuilder.Build()方法完成
1. 通过Host.CreateDefaultBuilder(args): 构造IHostBuilder的默认实现HostBuilder
在CreateHostBuilder方法内部,首先调用了Host.CreateDefaultBuilder构造了一个HostBuilder,这个我们先看下源码,看看到底Host类内部做了什么操作:
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) =>
{
var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 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) =>
{
var 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();
}
})
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
}); return builder;
}
从上述.NET Core源代码中,可以看到CreateDefaultBuilder内部构造了一个HostBuilder,同时设置了:
- 将内容根目录(contentRootPath)设置为由 GetCurrentDirectory 返回的路径。
- 通过以下源加载主机配置
- 环境变量(DOTNET_前缀)配置
- 命令行参数配置
- 通过以下对象加载应用配置
- appsettings.json
- appsettings.{Environment}.json
- 密钥管理器 当应用在 Development 环境中运行时
- 环境变量
- 命令行参数
- 添加日志记录提供程序
- 控制台
- 调试
- EventSource
- EventLog( Windows环境下)
- 当环境为“开发”时,启用范围验证和依赖关系验证。
以上构造完成了HostBuilder,针对ASP.NET Core应用,代码继续调用了HostBuilder.ConfigureWebHostDefaults方法。
2. IHostBuilder.ConfigureWebHostDefaults:通过GenericWebHostBuilder对HostBuilder增加ASP.NET Core的运行时设置
构造完成HostBuilder之后,针对ASP.NET Core应用,继续调用了HostBuilder.ConfigureWebHostDefaults方法。这是一个ASP.NET Core的一个扩展方法:
我们继续看ConfigureWebHostDefaults扩展方法内部做了哪些事情:
ASP.NET Core源码连接:https://github.com/dotnet/aspnetcore/blob/master/src/DefaultBuilder/src/GenericHostBuilderExtensions.cs
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore; namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Extension methods for configuring the IWebHostBuilder.
/// </summary>
public static class GenericHostBuilderExtensions
{
/// <summary>
/// Initializes a new instance of the <see cref="IWebHostBuilder"/> class with pre-configured defaults.
/// </summary>
/// <remarks>
/// The following defaults are applied to the <see cref="IWebHostBuilder"/>:
/// use Kestrel as the web server and configure it using the application's configuration providers,
/// adds the HostFiltering middleware,
/// adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,
/// and enable IIS integration.
/// </remarks>
/// <param name="builder">The <see cref="IHostBuilder" /> instance to configure</param>
/// <param name="configure">The configure callback</param>
/// <returns>The <see cref="IHostBuilder"/> for chaining.</returns>
public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
return builder.ConfigureWebHost(webHostBuilder =>
{
WebHost.ConfigureWebDefaults(webHostBuilder); configure(webHostBuilder);
});
}
}
}
© 2020 GitHub, Inc.
首先,通过类GenericHostWebHostBuilderExtensions,对IHostBuilder扩展一个方法:ConfigureWebHost:builder.ConfigureWebHost
在这个扩展方法中实现了对IWebHostBuilder的依赖注入:即将GenericWebHostBuilder实例传入方法ConfigureWebHostDefaults内部
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Hosting
{
public static class GenericHostWebHostBuilderExtensions
{
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
var webhostBuilder = new GenericWebHostBuilder(builder);
configure(webhostBuilder);
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
return builder;
}
}
}
通过GenericWebHostBuilder的构造函数GenericWebHostBuilder(buillder),将已有的HostBuilder增加了ASP.NET Core运行时设置。
可以参考ASP.NET Core源代码:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs
先看到这,让我们回到ConfigureWebHostDefaults:
将上面两段代码合并一下进行理解:ConfigureWebHostDefaults做了两件事情:
①. 扩展IHostBuilder增加ConfigureWebHost,引入IWebHostBuilder的实现GenericWebHostBuilder,将已有的HostBuilder增加ASP.NET Core运行时的设置。
② ConfigureWebHost代码中的configure(webhostBuilder):对注入的IWebHostBuilder,调用 WebHost.ConfigureWebDefaults(webHostBuilder),启用各类设置,如下代码解读:
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.ConfigureServices((hostingContext, services) =>
{
// Fallback
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
// "AllowedHosts": "localhost;127.0.0.1;[::1]"
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
// Fall back to "*" to disable.
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
// Change notification
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default. Clear that restriction because forwarders are
// being enabled by explicit configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
}); services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
} services.AddRouting();
})
.UseIIS()
.UseIISIntegration();
}
其内部实现了:
- 前缀为 ASPNETCORE_ 的环境变量加载主机配置。
- 将Kestrel作为默认的Web服务器
- 添加HostFiltering中间件(主机筛选中间件)
- 如果ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,添加转接头中间件ForwardedHeaders
- 启用IIS集成
3. 返回ConfigureWebHostDefaults代码中的configure(webHostBuilder):执行Program类中的webBuilder.UseStartup<Startup>();
以上过程完成了IHostBuilder.ConfigureWebHostDefaults,通过GenericWebHostBuilder对HostBuilder增加ASP.NET Core的运行时设置。
接下来就是主机的Build过程了:
4. CreateHostBuilder(args).Build()
CreateHostBuilder返回的IHostBuilder,我们通过代码Debug,看一下具体的类型:Microsoft.Extensions.Hosting.HostBuilder。
具体的Build过程是怎么样的?先看下Build的源码:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/HostBuilder.cs
主机Build的过程主要完成了:
- BuildHostConfiguration: 构造配置系统,初始化 IConfiguration _hostConfiguration;
- CreateHostingEnvironment:构建主机HostingEnvironment环境信息,包含ApplicationName、EnvironmentName、ContentRootPath等
- CreateHostBuilderContext:创建主机Build上下文HostBuilderContext,上下文中包含:HostingEnvironment和Configuration
- BuildAppConfiguration:构建应用程序配置
- CreateServiceProvider:创建依赖注入服务提供程序, 即依赖注入容器
四、主机是如何启动运行的
我们先通过Debug,看一下Host的信息:Microsoft.Extensions.Hosting.Internal.Host
这个Run方法也是一个扩展方法:HostingAbstractionsHostExtensions.Run
ASP.NET Core源代码链接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs
其实内部转调的还是Host.StartAsync方法,在内部启动了DI依赖注入容器中所有注册的服务。
.NET Core代码链接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Internal/Host.cs
五、主机中注册一个托管服务
以一个后台自更新(每隔5s 检查一次程序变更、进行输出)场景作为Demo,我们看一下如何在主机中注册一个托管服务。
自更新服务UpdateService,需要继承接口IHostService。
public class UpdateService : IHostedService
{
Task updateTask = null; CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); public Task StartAsync(CancellationToken cancellationToken)
{
updateTask = Task.Run(() =>
{
while (cancellationTokenSource.Token.IsCancellationRequested==false)
{
//Check new data...
Console.WriteLine(DateTime.Now + ": Executed");
Task.Delay(5000).Wait();
}
}); return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
cancellationTokenSource.Cancel(); return Task.CompletedTask;
}
}
同时,我们需要在ConfigureServices方法中,将UpdateService添加到IoC服务容器中
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHostedService, UpdateService>();
services.AddControllers();
}
程序启动后,可以看到以下输出:
以上是对.NET Core主机的概念、设计初衷、构建过程、启动运行过程、服务注册的整理和分享。
周国庆
2020/4/18
.NET Core技术研究-主机的更多相关文章
- ASP.NET Core技术研究-全面认识Web服务器Kestrel
因为IIS不支持跨平台的原因,我们在升级到ASP.NET Core后,会接触到一个新的Web服务器Kestrel.相信大家刚接触这个Kestrel时,会有各种各样的疑问. 今天我们全面认识一下ASP. ...
- .NET Core技术研究系列-索引篇
随着.NET Core相关技术研究的深入,现在将这一系列的文章,整理到一个索引页中,方便大家翻阅查找,同时,后续也会不断补充进来. .NET Core技术研究-WebApi迁移ASP.NET Core ...
- ASP.NET Core技术研究-探秘Host主机启动过程
当我们将原有ASP.NET 应用程序升级迁移到ASP.NET Core之后,我们发现代码工程中多了两个类Program类和Startup类. 接下来我们详细探秘一下通用主机Host的启动过程. 一.P ...
- .Net Core技术研究-Span<T>和ValueTuple<T>
性能是.Net Core一个非常关键的特性,今天我们重点研究一下ValueTuple<T>和Span<T>. 一.方法的多个返回值的实现,看ValueTuple<T> ...
- .Net Core技术研究-WebApi迁移ASP.NET Core2.0
随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的ASP.NET WebApi站点,就可以跨平台运行在Linux中.我们有必要先说一下ASP.NET Core. ASP ...
- .NET Core技术研究-配置读取
升级ASP.NET Core后,配置的读取是第一个要明确的技术.原先的App.Config.Web.Config.自定义Config在ASP.NET Core中如何正常使用.有必要好好总结整理一下,相 ...
- .NET Core技术研究-中间件的由来和使用
我们将原有ASP.NET应用升级到ASP.NET Core的过程中,会遇到一个新的概念:中间件. 中间件是ASP.NET Core全新引入的概念.中间件是一种装配到应用管道中以处理请求和响应的软件. ...
- ASP.NET Core技术研究-探秘依赖注入框架
ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务.提供服务.依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者. 毫不夸张的说,ASP.NET ...
- .NET Core技术研究-通过Roslyn代码分析技术规范提升代码质量
随着团队越来越多,越来越大,需求更迭越来越快,每天提交的代码变更由原先的2位数,暴涨到3位数,每天几百次代码Check In,补丁提交,大量的代码审查消耗了大量的资源投入. 如何确保提交代码的质量和提 ...
随机推荐
- ECharts的使用与总结
ECharts的使用与总结 一,介绍与需求 1.1,介绍 ECharts商业级数据图表,一个纯Javascript的图表库,可以流畅的运行在PC和移动设备上,兼容当前绝大部分浏览器(IE6/7/8/9 ...
- python多重继承的属性和方法调用顺序问题和对迭代器的初步理解
推荐阅读:https://www.cnblogs.com/bigb/p/11650707.html 计算机学习的一个好办法就是自己将代码跑一遍,了解代码的运作顺序和原理(主要弄懂 函数作用,传入参数, ...
- Web的服务器和Javaweb结构
上一节介绍了Eclipse中集成Tomcat环境搭建及javaweb项目的创建,下面说说什么是web服务器及javaweb的结构. 1.web应用的演变 1.1 b/s与c/s模式 B/S:Brows ...
- FormData/Go分片/分块文件上传
FormData 接口提供了一种表示表单数据的键值对的构造方式,经过它的数据可以使用 XMLHttpRequest.send() 方法送出,本接口和此方法都相当简单直接.如果送出时的编码类型被设为 & ...
- 《大空头》与A股内幕消息
目录 <大空头>简介 投行人士透露内幕消息不划算 <大空头>里合规性的一些解释 相信A股内幕消息的一些惨痛教训. 风险提示. <大空头>简介 <大空头> ...
- dvwa学习之七:SQL Injection
1.Low级别 核心代码: <?php if( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; ...
- vue基础指令学习
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 使用一行Python代码从图像读取文本
处理图像不是一项简单的任务.对你来说,作为一个人,很容易看着某样东西然后马上知道你在看什么.但电脑不是这样工作的. 对你来说太难的任务,比如复杂的算术,或者一般意义上的数学,是计算机毫不费力就能完成的 ...
- coding++:mybatis update foreach (SQL循环)批量更新
今天要做批量更新的业务,采用 mybaits 的 foreach 动态语句,遇到一些问题做下记录. 参考示例(1): <update id="" parameterType= ...
- coding++:MD5加密(JAVA加密 与 JS加密不一致问题)
要求:根据指定 字符加密 JS中的加密方法 要和 JAVA中的算法保持一致,解决如下: var rotateLeft = function (lValue, iShiftBits) { retur ...