在ASP.NET Core中使用托管启动(hosting startup)程序集,实现批量注册service
在启动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<startup>(); });
如果阻止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<serviceattribute>() != 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<serviceattribute>();
//根据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<startup>(); });
这样我们在controller中就可以使用已经自动注册到servicecontainer中的service了。
public class homecontroller : controller { private readonly ilogger<homecontroller> _logger; private readonly iuser1 _userservice1; private readonly iuser _userservice; public homecontroller(ilogger<homecontroller> 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<exception>(); // 执行 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<hostingstartupattribute>()) {
//实例化自定义的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); }