网站优化排名多少钱,国外自建站好做吗,做英文网站公司,昆明做烤瓷牙哪去柏德L网站概念在ASP.NET Core中我们可以使用一种机制来增强启动时的操作#xff0c;它就是HostingStartup。如何叫增强操作#xff0c;相信了解过AOP概念的同学应该都非常的熟悉。我们常说AOP使用了关注点分离的方式#xff0c;增强了对现有逻辑的操作。而我们今天要说的… 概念 在ASP.NET Core中我们可以使用一种机制来增强启动时的操作它就是HostingStartup。如何叫增强操作相信了解过AOP概念的同学应该都非常的熟悉。我们常说AOP使用了关注点分离的方式增强了对现有逻辑的操作。而我们今天要说的HostingStartup就是为了增强启动操作这种增强的操作甚至可以对对现有的程序可以做到无改动的操作。例如外部程序集可通过HostingStartup实现为应用提供配置服务、注册服务或中间件管道操作等。使用方式 HostingStartup属性表示要在运行时激活的承载启动程序集。大致分为两种情况一种是自动扫描当前Web程序集中通过HostingStartup指定的类另一种是手动添加配置hostingstartupassembles指定外部的程序集中通过HostingStartup指定的类。第一种方式相对简单但是对Web程序本身有入侵第二种方式稍微复杂一点点但是可以做到对现有代码无入侵操作接下来我们分别演示这两种使用方式。ASP.NET Core中直接定义首先是在ASP.NET Core程序中直接使用HostingStartup,这种方式比较简单首先在Web程序中随便定义一个类,然后实现IHostingStartup接口最后别忘了在程序集中添加HostingStartupAttribute指定要启动的类的类型具体代码如下所示using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
//通过HostingStartup指定要启动的类型
[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))]
namespace HostStartupWeb
{public class HostingStartupInWeb : IHostingStartup{public void Configure(IWebHostBuilder builder){//程序启动时打印依据话代表执行到了这里Debug.WriteLine(Web程序中HostingStartupInWeb类启动);//可以添加配置builder.ConfigureAppConfiguration(config {//模拟添加一个一个内存配置var datas new ListKeyValuePairstring, string{new KeyValuePairstring, string(ServiceName, HostStartupWeb)};config.AddInMemoryCollection(datas);});//可以添加ConfigureServicesbuilder.ConfigureServices(services {//模拟注册一个PersonDtoservices.AddScoped(providernew PersonDto { Id 1, Name yi念之间, Age 18 });});//可以添加Configurebuilder.Configure(app {//模拟添加一个中间件app.Use(async (context, next) {await next();});});}}
}
仅仅使用上面所示的这些代码便可在Web程序启动的时候去自动执行HostingStartupInWeb的Configure方法在这里面我们几乎可以使用所有针对ASP.NET Core程序配置的操作而且不需要在Web程序中额外添加别的代码就可以自动调用HostingStartupInWeb的Configure方法。外部程序集引入我们之前也说过上面的方式虽然使用起来相对简单一点仅仅是一点那就是省去了指定启动程序集的逻辑。但是上面的方式需要在Web程序中添加这样的话还是会修改代码。而且可能更多的时候我们是在外部的程序集中编写HostingStartup逻辑这时候就需要使用另一种方式在将外部程序集中引入HostingStartup。首先我们要在自定义的程序集中至少引入Microsoft.AspNetCore.Hosting包才能使用HostingStartupPackageReference IncludeMicrosoft.AspNetCore.Hosting Version2.2.7 /
如果你不需要使用注册中间件的逻辑那么仅仅引入Microsoft.AspNetCore.Hosting.Abstractions即可PackageReference IncludeMicrosoft.AspNetCore.Hosting.Abstractions Version2.2.0 /
如果需要使用其他功能包可以自行在定义的程序集中引入。比如我们定义了一个名为HostStartupLib的Standard类库并创建了名为HostStartupLib的类using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: HostingStartup(typeof(HostStartupLib.HostingStartupInLib))]
namespace HostStartupLib
{public class HostingStartupInLib : IHostingStartup{public void Configure(IWebHostBuilder builder){Debug.WriteLine(Lib程序中HostingStartupInLib类启动);//添加配置builder.ConfigureAppConfiguration((context, config) {var datas new ListKeyValuePairstring, string{new KeyValuePairstring, string(ServiceName, HostStartupLib)};config.AddInMemoryCollection(datas);});//添加ConfigureServicesbuilder.ConfigureServices(services {services.AddScoped(providernew PersonDto { Id 2, Name er念之间, Age 19 });});//添加Configurebuilder.Configure(app {app.Use(async (context, next) {await next();});});}}
}
然后我们将自定义的HostStartupLib这个Standard类库引入Web项目中运行Web程序发现HostingStartupInLib的Configure方法并不能被调用。其实我们上面说过了将HostingStartup从外部程序集引入的话需要手动指定启动程序集的名称。指定启动程序集的方式有两种一种是指定IWebHostBuilder的扩展UseSetting指定public static IHostBuilder CreateHostBuilder(string[] args) Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder {//通过UseSetting的方式指定程序集的名称webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, HostStartupLib);//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, HostStartupLib;HostStartupLib2);webBuilder.UseStartupStartup();});
另一种通过添加环境变量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES的方式可以通过设置launchSettings.json中environmentVariables: {ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: HostStartupLib//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: HostStartupLib;HostStartupLib2
}
可以引入多个包含HostingStartup的程序集在设置WebHostDefaults.HostingStartupAssembliesKey或者ASPNETCORE_HOSTINGSTARTUPASSEMBLIES指定多个程序集名称可以使用英文分号(;)隔开程序集名称。虽然是两种形似指定但是其实本质是一样的那就是设置配置key为hostingStartupAssemblie配置的值下面我们会详细讲解。 通过在程序中设置环境变量的方式等同于Window系统中Set的方式设置环境变量或Linux系统中export的方式设置环境变量亦或是直接设置系统环境变量效果都是一致的。指定完成启动程序集之后再次运行程序便可以看到HostingStartupInLib的Configure方法被调用到了。在这里我们可以看到如果是使用的环境变量的方式去指定启动程序集的话对现有代码可以做到完全无入侵。源码探究在上面我们简单的介绍了HostingStartup的概念及基本的使用方式基于这些我们产生了几个疑问首先是关于HostingStartup的基本工作方式是什么其次是为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到而通过外部程序集引入HostingStartup需要手动指定程序集最后是通过外部程序集引入HostingStartup的指定方式为何只能是UseSetting和环境变量的方式基于以上几个疑问我们来探索一下HostingStartup的相关源码来揭开它的神秘面纱。首先废话不多说直接找到源码位置[点击查看源码????]在GenericWebHostBuilder类中的ExecuteHostingStartups方法中关于GenericWebHostBuilder类我们在上篇文章深入探究ASP.NET Core Startup初始化中主要就是分析这个类因为这是构建WebHost的默认类而我们接下来要说的ExecuteHostingStartups方法也是承载在这个类中直接贴代码如下所示private void ExecuteHostingStartups()
{//通过配置_config和当前程序集名称构建WebHostOptions类var webHostOptions new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);//如果PreventHostingStartup属性为true则直接返回//通过这个可以配置阻止启动逻辑if (webHostOptions.PreventHostingStartup){return;}var exceptions new ListException();//构建HostingStartupWebHostBuilder_hostingStartupWebHostBuilder new HostingStartupWebHostBuilder(this);//GetFinalHostingStartupAssemblies获取最终要执行的程序集名称foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)){try{//通过程序集名称加载程序集信息因为使用了AssemblyName所以只需要使用程序集名称即可var assembly Assembly.Load(new AssemblyName(assemblyName));//获取包含HostingStartupAttribute的程序集foreach (var attribute in assembly.GetCustomAttributesHostingStartupAttribute()){//实例化HostingStartupAttribute的HostingStartupType属性的对象实例//即我们上面声明的[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))]var hostingStartup (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);//调用HostingStartup的Configure方法hostingStartup.Configure(_hostingStartupWebHostBuilder);}}catch (Exception ex){exceptions.Add(new InvalidOperationException($Startup assembly {assemblyName} failed to execute. See the inner exception for more details., ex));}}if (exceptions.Count 0){_hostingStartupErrors new AggregateException(exceptions);}
}
通过上面的源码我们就可以很清楚的了解到HostingStartup的基本工作方式。获取的程序集中包含的HostingStartupAttribute通过获取HostingStartupAttribute的HostingStartupType属性得到要执行的IHostingStartup实例最后执行Configure方法Configure方法需要传递IWebHostBuilder的实例而HostingStartupWebHostBuilder正是实现了IWebHostBuilder接口。 我们了解到了HostStartup的工作方式接下来我们来探究一下为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到而通过外部程序集引入HostingStartup需要手动指定程序集。通过上面的源码我们可以得到一个信息那就是所有需要启动的程序集信息都是来自WebHostOptions的GetFinalHostingStartupAssemblies方法接下来我们就来查看一下GetFinalHostingStartupAssemblies方法的实现源码[点击查看源码????]public IEnumerablestring GetFinalHostingStartupAssemblies()
{return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase);
}
从这里我们可以看出程序集信息来自于HostingStartupAssemblies属性而且还要排除掉HostingStartupExcludeAssemblies包含的程序集。我们找到他们初始化的相关逻辑大致如下//承载启动是需要调用的HostingStartup程序集
public IReadOnlyListstring HostingStartupAssemblies { get; set; }
//承载启动时排除掉不不要执行的程序集
public IReadOnlyListstring HostingStartupExcludeAssemblies { get; set; }
//是否阻止HostingStartup启动执行功能如果设置为false则HostingStartup功能失效
//通过上面的ExecuteHostingStartups方法源码可知
public bool PreventHostingStartup { get; set; }
//应用程序名称
public string ApplicationName { get; set; }public WebHostOptions(IConfiguration configuration, string applicationNameFallback)
{ApplicationName configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback;HostingStartupAssemblies Split(${ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]});HostingStartupExcludeAssemblies Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);PreventHostingStartup WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey);
}//分隔配置的程序集信息,分隔依据为;分号,这也是我们上面说过配置多程序集的时候采用分号分隔的原因
private IReadOnlyListstring Split(string value)
{return value?.Split(;, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)?? Array.Emptystring();
}
首先,通过HostingStartupAssemblies的初始化逻辑我们可以得出默认会是有两个数据来源一个是当前的ApplicationName另一个是通过HostingStartupAssembliesKey配置的程序集信息。这也解答了我们上面说过的为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到而通过外部程序集引入HostingStartup需要手动指定程序集。其次,我们可以了解到通过配置HostingStartupExcludeAssemblies信息排除你不想启动的HostingStartup程序集而且还可以通过配置PreventHostingStartup值来禁止使用HostingStartup的功能。通过上面的代码我们还了解到这三个属性的来源的配置名称都是来自WebHostDefaults这个常量类接下来我们查看一下这三个属性对应的配置名称public static readonly string HostingStartupAssembliesKey hostingStartupAssemblies;
public static readonly string HostingStartupExcludeAssembliesKey hostingStartupExcludeAssemblies;
public static readonly string PreventHostingStartupKey preventHostingStartup;
也就是说我们可以可以通过配置这三个名称的配置来完成HostingStartup相关的功能比如public static IHostBuilder CreateHostBuilder(string[] args) Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder {//通过UseSetting的方式指定程序集的名称webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, HostStartupLib);//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, HostStartupLib;HostStartupLib2);//排除执行HostStartupLib2程序集执行HostingStartup逻辑webBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, HostStartupLib2);//禁用HostingStartup功能webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, true);webBuilder.UseStartupStartup();});
或通过环境变量的方式去操作environmentVariables: {ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: HostStartupLib,//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2//ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: HostStartupLib;HostStartupLib2//排除执行HostStartupLib2程序集执行HostingStartup逻辑ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIESHostStartupLib2,//禁用HostingStartup功能ASPNETCORE_PREVENTHOSTINGSTARTUP:true
}
其实这两种配置方式是完全等价的为什么这么说呢首先是在Configuration中获取配置是忽略大小写的其实是使用ConfigureWebHostDefaults配置WebHost相关信息的时候会添加configBuilder.AddEnvironmentVariables(prefix: ASPNETCORE_)逻辑这样的话获取环境变量的时候可以忽略ASPNETCORE_前缀。那么到目前为止还有一个疑问尚未解决那就是为何只能通过UseSetting和环境变量的方式去配置HostingStartup相关配置解铃还须系铃人我们在上面的ExecuteHostingStartups方法中看到了这个逻辑//这里传递了一个_config
var webHostOptions new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
我们可以看到传递了配置Configuration的实例_config我们到初始化_config地方有如下逻辑var configBuilder new ConfigurationBuilder().AddInMemoryCollection();
if (!options.SuppressEnvironmentConfiguration)
{//添加环境变量configBuilder.AddEnvironmentVariables(prefix: ASPNETCORE_);
}
//构建了_config实例
private readonly IConfiguration _config configBuilder.Build();
也就可以解释为何我们可以通过环境变量去配置HostingStartup然后我们再来看UseSetting方法的逻辑public IWebHostBuilder UseSetting(string key, string value)
{_config[key] value;return this;
}
原来UseSetting也是给_config实例设置值所以无论通过UseSetting或环境环境变量的方式去配置本质都是在操作_config这个配置实例到此为止所有谜团均以解开。在SkyAPM中的使用我们上面说了HostingStartup可以增强启动时候的操作可以通过对现有代码无入侵的方式增强程序功能。而SkyAPM-dotnet也正是使用了这个功能实现了无入侵启动APM监控。我们来回顾一下SkyAPM-dotnet的使用方式首先是使用Nuget添加SkyAPM.Agent.AspNetCore程序集引用。其次是在launchSettings.json文件中添加ASPNETCORE_HOSTINGSTARTUPASSEMBLIES:SkyAPM.Agent.AspNetCore环境变量配置(等同于set ASPNETCORE_HOSTINGSTARTUPASSEMBLIESSkyAPM.Agent.AspNetCore或export ASPNETCORE_HOSTINGSTARTUPASSEMBLIESSkyAPM.Agent.AspNetCore的方式本质都是在配置环境变量)最后通过SKYWALKING__SERVICENAME设置程序名称这里我们通过需要配置ASPNETCORE_HOSTINGSTARTUPASSEMBLIES名称可以看出确实是使用了HostingStartup功能而通过HostingStartup增强的操作入口肯定就在SkyAPM.Agent.AspNetCore程序集中我们找到SkyAPM.Agent.AspNetCore程序集的源码[点击查看源码????]看到了SkyApmHostingStartup类实现如下using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using SkyApm.Agent.AspNetCore;
using SkyApm.AspNetCore.Diagnostics;[assembly: HostingStartup(typeof(SkyApmHostingStartup))]namespace SkyApm.Agent.AspNetCore
{internal class SkyApmHostingStartup : IHostingStartup{public void Configure(IWebHostBuilder builder){builder.ConfigureServices(services services.AddSkyAPM(ext ext.AddAspNetCoreHosting()));}}
}
通过这个我们可以看出确实如此当然也是等同于我们通过UseSetting(WebHostDefaults.HostingStartupAssembliesKey, SkyApm.Agent.AspNetCore)去配置我们甚至可使用如下的方式去使用SkyAPM-dotnetpublic void ConfigureServices(IServiceCollection services)
{services.AddSkyAPM(ext ext.AddAspNetCoreHosting())
}
这些写法其实是完全等价的但是通过环境变量的方式配置HostingStartup启动程序集的方式无疑是最优雅的。所以我们在日常的学习开发中最好还是通过这种方式去操作。改造Zipkin使用我们在之前的文章ASP.NET Core整合Zipkin链路跟踪中曾演示过基于诊断日志DiagnosticSource改进Zipkin的集成方式通过本篇文章讲述的HostingStartup我们可以进步一改进Zipkin的集成方式可以让它使用起来和SkyAPM-dotnet类似的方式我们基于之前的示例中的ZipkinExtensions程序集中添加一个ZipkinHostingStartup类用于承载集成Zipkin的操作代码如下using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;namespace ZipkinExtensions
{public class ZipkinHostingStartup: IHostingStartup{public void Configure(IWebHostBuilder builder){builder.ConfigureServices(services {services.AddZipkin();services.AddSingletonITraceDiagnosticListener, HttpDiagnosticListener();});builder.Configure(app {IHostApplicationLifetime lifetime app.ApplicationServices.GetServiceIHostApplicationLifetime();ILoggerFactory loggerFactory app.ApplicationServices.GetServiceILoggerFactory();IConfiguration configuration app.ApplicationServices.GetServiceIConfiguration();string serivceName configuration.GetValuestring(ServiceName);string zipKinUrl configuration.GetValuestring(ASPNETCORE_ZIPKINADDRESS);app.UseZipkin(lifetime, loggerFactory, serivceName, zipKinUrl);});}}
}
然后在每个项目的launchSettings.json文件中添加如下所示的配置即可这样的话就可以做到对现有业务代码无任何入侵。 environmentVariables: {ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: ZipkinExtensions,ASPNETCORE_ZIPKINADDRESS: http://localhost:9411/}
总结 本文介绍了HostingStartup的基本概念基础使用以及对其源码的分析和在SkyAPM-dotnet中的应用最后我们改造了Zipkin的集成方式。HostingStartup在一些集成APM或者链路跟踪的类似场景还是非常实用的或者如果我们有集成一些基础组件或者三方的组件但是我们的代码中并不需要直接的使用这些组件中的类或者直接的代码关系均可以使用HostingStartup的方式去集成为我们实现对现有代码提供无入侵增强提供了强大的支持。关于HostingStartup我也是在看源码中无意发现的后来发现微软ASP.NET Core官方文档Use hosting startup assemblies in ASP.NET Core一文中有讲解然后联想到自己使用过的SkyAPM-dotnet正是使用了HostingStartup诊断日志DiagnosticSource的方式实现了对代码无入侵的方式进行监控和链路跟踪。于是决定深入研究一下可谓收获满满便写下这篇文章希望更多的人能够了解使用这个功能。????欢迎扫码关注我的公众号????