在启动ASPNET Core时可以从外部程序集向应用添加增强功能。例如,外部库可以用托管启动( hosting startup) 实现为应用程序提供附加配置(Configuration)或服务(service)。
具体实现如下:
1、实现 IHostingStartup 接口
2、标注程序集(HostingStartup)属性。
[assembly: HostingStartup(typeof(StartupEnhancement.StartupEnhancementHostingStartup))]
3、在CreateHostBuilder中配置加载的程序集,如果多个程序集 分号 隔开
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { //增加外部启动项Fap.Core.DI.ServicesInjection,初始化所有service webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey,"Fap.Core") .UseStartup(); });
如果阻止Hosting Startup加载,需要以下设置
webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
如果排除某些程序集的Hosting Startup加载
webBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "ASSEMBLY1;ASSEMBLY2; ...")
多个程序集 分号 隔开。
ASPNET Core 中的 DI 没有批量注册service的功能。下面我就实现一个批量注册service的功能。
采用注解的形式,在需要注册为service的类上进行标注。
定义一个Attribute
[AttributeUsage(AttributeTargets.Class)] public class ServiceAttribute:Attribute { public ServiceAttribute(ServiceLifetime serviceLifetime) { ServiceLifetime = serviceLifetime; } public ServiceLifetime ServiceLifetime { get; set; } = ServiceLifetime.Transient; }
定义需要注册的类和接口
public interface IUser { string Get(string userName); } public interface IUser1 { string Get1(string userName); } [Service(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton)] public class User : IUser,IUser1 { private string s = Guid.NewGuid().ToString(); public string Get(string userName) { return $"{s}===={userName}"; } public string Get1(string userName) { return s; } }
如上,在User类上标注ServiceAttribute属性,设置ServiceLifetime为单利模式ServiceLifetime.Singleton
接下来实现 利用host startup来实现自动注册功能。根据class上标注的service attribute 来自动注册service
//标注程序集属性 HostingStartup
[assembly: HostingStartup(typeof(Fap.Core.DI.ServicesInjection))] namespace Fap.Core.DI { public class ServicesInjection : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureServices(services => { var basePath = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; var assemblies = Directory.GetFiles(basePath, "*.dll").Select(Assembly.LoadFrom).ToArray(); var types = assemblies.SelectMany(a => a.DefinedTypes).Select(type => type.AsType()).Where(t => t.GetCustomAttribute() != null).ToArray(); var implementTypes = types.Where(t => t.IsClass).ToArray(); foreach (var implementType in implementTypes) { var interfaceTypes = implementType.GetInterfaces(); foreach (var interfaceType in interfaceTypes) { var attr = implementType.GetCustomAttribute ();
//根据serviceLifetime来向容器中注册 _ = (attr.ServiceLifetime switch { ServiceLifetime sl when sl == ServiceLifetime.Scoped => services.AddScoped(interfaceType, implementType), ServiceLifetime sl when sl == ServiceLifetime.Singleton => services.AddSingleton(interfaceType, implementType), ServiceLifetime sl when sl == ServiceLifetime.Transient => services.AddTransient(interfaceType, implementType), _ => throw new FileNotFoundException("未找到此类型") }); } } }); } } }
下面在Host builder时设置HostStartup
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { //增加外部启动项Fap.Core.DI.ServicesInjection,初始化所有service webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey,"Fap.Core") .UseStartup(); });
这样我们在Controller中就可以使用已经自动注册到servicecontainer中的service了。
public class HomeController : Controller { private readonly ILogger_logger; private readonly IUser1 _userService1; private readonly IUser _userService; public HomeController(ILogger logger,IUser1 userService1,IUser user) { _logger = logger; _userService1 = userService1; _userService = user; } public IActionResult Index() { ViewBag.CC = _userService.Get("zhangsan")+_userService1.Get1("lisi"); return View(); } }
除了利用IHostingStartup为应用提供服务注册,还可以提供额外配置。
[assembly: HostingStartup(typeof(HostingStartupLibrary.ServiceKeyInjection))] namespace HostingStartupLibrary { public class ServiceKeyInjection : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureAppConfiguration(config => { var dict = new Dictionary<string, string> { {"DevAccount_FromLibrary", "DEV_1111111-1111"}, {"ProdAccount_FromLibrary", "PROD_2222222-2222"} }; config.AddInMemoryCollection(dict); }); } } }
在controller中就可以访问到如上配置项
public IndexModel(IConfiguration config) { ServiceKey_Development_Library = config["DevAccount_FromLibrary"]; ServiceKey_Production_Library = config["ProdAccount_FromLibrary"]; }
-----------------------------------------------------------------------------------------------------------
具体源码实现:
webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey,"Fap.Core")
设置了WebHostOptions中的HostingStartupAssemblies属性,存放我们要加载的IHostingStartup的程序集。
在IWebHostBuilder 调用 Builder()返回IWebHost方法中进行调用。下面为关键代码
_options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
//没有设置阻止加载webBuilder.UseSetting( WebHostDefaults.PreventHostingStartupKey, "false")
if (!_options.PreventHostingStartup)
{
var exceptions = new List();
// 执行 hosting startup assemblies
foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
foreach (var attribute in assembly.GetCustomAttributes())
{
//实例化自定义的hostingStartup
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
//调用Configure方法,执行我们自定的逻辑
hostingStartup.Configure(this);
}
}
catch (Exception ex)
{
// Capture any errors that happen during startup
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);
}
}
_options.GetFinalHostingStartupAssemblies()方法代码如下:
public IEnumerable<string> GetFinalHostingStartupAssemblies() {
//返回HostingStartupAssemblies中排除掉HostingStartupExcludeAssemblies的程序集 return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase); }