在《通过自定义配置实现插件式设计》中,通过在运行时对配置的动态解析实现了真正的“插件式”设计,其本质就是让配置自行提供对配置类型实例的创建。在这篇文章中,我们将更进一步,让自定义配置和IoC集成起来。IoC的目的就是通过解析注册的依赖注入信息,最终创建出我们希望的某个对象。而只有通过配置的方式来定义IoC容器需要的注入信息,才能实现灵活的设计。所以,如果将两者集成起来,让IoC容器能够解析通过配置定义的“依赖注入”信息,具有很大的现实意义。接下来,我们将通过Unity为例,介绍IoC和自定义进行无缝集成的实现方案。例子源代码可以从这里下载(可以直接运行)
目录 一、如果IoC能够解析自定义配置定义的“注入”… 二、整个配置结构的定义 三、配置项如何提供“类型注册”信息 四、如何为UnityContainer进行“类型注册”
我们先来看看如果一个IoC容器能够解析通过自定义配置定义的注入信息,会为我们的设计和编程带来怎样的改变。在这里我采用的是微软Unity这种轻量级的IoC容器,并沿用《.NET的资源并不限于.resx文件,你可以采用任意存储形式》中介绍的应用场景——基于多种存储方式的资源管理框架。现在只关注与资源的读取,我们将基于不同存储形式的资源读取操作实现在相应的ResourceProovider中,它们实现如下一个简单的IResourceProvider接口。
1: public interface IResourceProvider 2: { 3: object GetObject(string key); 4: }
然后我们创建两个具体的ResourceProvider:DbResourceProvider和XmlResourceProvider,它们分别基于数据库表和XML文件的资源存储形式。DbResourceProvider需要连接数据库,需要引用配置的连接字符串,所以有一个ConnectionStringName属性;而XmlResourceProvider需要访问具体的XML文件,FileName属性表示文件路径。
1: [ConfigurationElementType(typeof(DbResourceProviderData))] 2: public class DbResourceProvider : IResourceProvider 3: { 4: public string ConnectionStringName { get; private set; } 5: public DbResourceProvider(string connectionStringName) 6: { 7: this.ConnectionStringName = connectionStringName; 8: } 9: public object GetObject(string key) 10: { 11: throw new NotImplementedException(); 12: } 13: public override string ToString() 14: { 15: return string.Format("DbResourceProvider (ConnectionStringName = {0})", this.ConnectionStringName); 16: } 17: } 18: 19: [ConfigurationElementType(typeof(XmlResourceProviderData))] 20: ublic class XmlResourceProvider : IResourceProvider 21: 22: public string FileName { get; private set; } 23: public XmlResourceProvider(string fileName) 24: { 25: this.FileName = fileName; 26: } 27: public object GetObject(string key) 28: { 29: throw new NotImplementedException(); 30: } 31: public override string ToString() 32: { 33: return string.Format("XmlResourceProvider (FileName = {0})", this.FileName); 34: } 35: }
具体使用哪个ResourceProvider,通过配置来决定。整个配置定义在<artech.resources>配置节中,该配置节具有一个<providers>子节点,它定义了一系列ResourceProvider的列表。每个ResourceProvider配置具有两个相同的属性:Name和Type,以及一些自己专属的配置属性(比如DbResourceProvider的connectionStringName,XmlResourceProvider的fileName)。至于默认采用哪个Provider,则通过配置节的defaultProvider属性来决定。在本例中,我们默认采用的是dbProvider。
1: <?xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <configSections> 4: <section name="artech.resources" type="Artech.ResourceManagement.Configuration.ResourceSettings, Artech.ResourceManagement"/> 5: </configSections> 6: <artech.resources defaultProvider="dbProvider"> 7: <providers> 8: <add name="xmlProvider" type="Artech.ResourceManagement.XmlResourceProvider, Artech.ResourceManagement" fileName="C:\resources.xml"/> 9: <add name="dbProvider" type="Artech.ResourceManagement.DbResourceProvider, Artech.ResourceManagement" connectionStringName="localSqlSvr"/> 10: </providers> 11: </artech.resources> 12: </configuration>
现在我们通过创建的UnityContainer来创建相应的ResourceProvider。第一个Resolve<IResourceProvider>方法没有指定任何参数,表明创建的是配置的默认ResourceProvider(即DbProvider),后面两个通过指定ResourceProvider的配置名称(xmlProvider和dbProvider)来创建相应的ResourceProvider。
1: static void Main(string[] args) 2: { 3: IUnityContainer container = new UnityContainer(); 4: ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create()); 5: Console.WriteLine(container.Resolve<IResourceProvider>()); 6: Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider")); 7: Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider")); 8: }
执行结果:
1: DbResourceProvider (ConnectionStringName = localSqlSvr)
2: XmlResourceProvider (FileName = C:\resources.xml)
3: DbResourceProvider (ConnectionStringName = localSqlSvr)
接下来,我们先来看看上面给出的那段配置是如何定义的。我们整个配置定义在如下一个ResourceSettings类型中。ResourceSettings继承自SerializableConfigurationSection,该类型定义在EnterLib(实际上本文介绍的配置与IoC继承的方案来自于EnterLib)中,并实现了接口ITypeRegistrationsProvider。对接口ITypeRegistrationsProvider的实现很重要,我们放在下面一节进行单独介绍。
1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider 2: { 3: public const string ConfigSectionName = "artech.resources"; 4: 5: [ConfigurationProperty("defaultProvider", IsRequired = true)] 6: public string DefaultProvider 7: { 8: get { return (string)this["defaultProvider"]; } 9: } 10: 11: [ConfigurationProperty("providers", IsRequired = true)] 12: public NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase> Providers 13: { 14: get { return (NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>)this["providers"]; } 15: } 16: 17: public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource) 18: { 19: //省略... 20: } 21: 22: public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource) 23: { 24: return this.GetRegistrations(configurationSource); 25: } 26: 27: public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource) 28: { 29: //省略... 30: } 31: }
配置属性DefaultProvider表示默认的ResourceProvider配置名称,而Providers则表示配置的ResourceProvider列表,这是一个NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>类型的集合。该集合的每一个元素类型为ResourceProviderDataBase,它表示所有ResourceProvider配置类型的基类。ResourceProviderDataBase定义如下,它继承自NameTypeConfigurationElement类型(该类型定义在EnterLib配置系统中)。ResourceProviderDataBase定义的两个需方法用于类型注册之用,我们在下面一节进行单独介绍。
1: public class ResourceProviderDataBase : NameTypeConfigurationElement 2: { 3: protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 4: { 5: throw new NotImplementedException(); 6: } 7: public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource) 8: { 9: //省略... 10: } 11: }
具体的DbResourceProvider和XmlResourceProvider继承自ResourceProviderDataBase。它们除了定义自己对应的配置属性(connectionStringName和fileName),还需要重写GetCreationExpression这个虚方法。
1: public class DbResourceProviderData : ResourceProviderDataBase 2: { 3: [ConfigurationProperty("connectionStringName", IsRequired = true)] 4: public string ConnectionStringName 5: { 6: get { return (string)this["connectionStringName"]; } 7: } 8: 9: protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 10: { 11: //省略... 12: } 13: } 14: 15: public class XmlResourceProviderData : ResourceProviderDataBase 16: { 17: [ConfigurationProperty("fileName", IsRequired = true)] 18: public string FileName 19: { 20: get { return (string)this["fileName"]; } 21: } 22: protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 23: { 24: //省略... 25: } 26: }
所有类型的IoC容器的作用无外乎通过解析注册的各种依赖注入(构造器注入、属性注入和方法注入)通过基类或者接口创建和初始化某个具体类型的实例。Unity可以通过一个特殊的类型来表示依赖注入 信息:TypeRegistration。TypeRegistration定义如下,由于篇幅所限,在这里就不多作介绍了。
1: public class TypeRegistration 2: { 3: public TypeRegistration(LambdaExpression expression); 4: public TypeRegistration(LambdaExpression expression, Type serviceType); 5: 6: public IEnumerable<ParameterValue> ConstructorParameters { get; } 7: public Type ImplementationType { get; } 8: public IEnumerable<InjectedProperty> InjectedProperties { get; } 9: public bool IsDefault { get; set; } 10: public bool IsPublicName { get; set; } 11: public LambdaExpression LambdaExpression { get; } 12: public TypeRegistrationLifetime Lifetime { get; set; } 13: public string Name { get; set; } 14: public NewExpression NewExpressionBody { get; } 15: public Type ServiceType { get; } 16: 17: public static string DefaultName<TServiceType>(); 18: public static string DefaultName(Type serviceType); 19: }
现在我们的目的是让UnityContainer来解析定义在ResourceSettings这个配置类型中的“注入信息”,那么就需要ResourceSettings对象能够提供它一个完备的TypeRegistration列表,这些列表帮助UnityContainer进行正确的实例创建或者获取决策。现在我们就来介绍ResourceSettings如果为UnityContainer提供类型注册信息的,现在我们将关注点放在上面给出的代码中的省略部分。先来看看ResourceProviderDataBase的定义。
1: public class ResourceProviderDataBase : NameTypeConfigurationElement 2: { 3: protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 4: { 5: throw new NotImplementedException(); 6: } 7: public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource) 8: { 9: Expression<Func<IResourceProvider>> creationExpression = GetCreationExpression(configurationSource); 10: TypeRegistration providerTypeRegistration = new TypeRegistration<IResourceProvider>(creationExpression) 11: { 12: Name = this.Name, 13: IsPublicName = true, 14: Lifetime = TypeRegistrationLifetime.Singleton 15: }; 16: yield return providerTypeRegistration; 17: } 18: }
需方法GetRegistrations以IEnumerable<TypeRegistration>类型的方式获取所有基于该配置元素的类型注册信息,这里提供默认的实现——基于具体ResourceProvider类型的类型注册。在这里我们通过一个Expression<Func<IResourceProvider>>对象作为参数创建TypeRegistration对象,而传入的表达式用于具体ResourceProvider的创建。该Expression<Func<IResourceProvider>>对象通过需方法GetCreationExpression方法获取,所有具体的子类需要重写这个方法。(P.S. 为了避免对ResourceProvider的频繁创建,我通过Lifetime将其设置成Singleton形式)
对于继承自ResourceProviderDataBase的DbResourceProviderData和XmlResourceProviderData重写了GetCreationExpression方法,实现了对DbResourceProvider和XmlResourceProvider的创建。
1: public class DbResourceProviderData : ResourceProviderDataBase 2: { 3: //Others 4: protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 5: { 6: return () => new DbResourceProvider(this.ConnectionStringName); 7: } 8: } 9: 10: public class XmlResourceProviderData : ResourceProviderDataBase 11: { 12: //Others 13: protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource) 14: { 15: return () => new XmlResourceProvider(this.FileName); 16: } 17: }
我们说ResourceSettings实现了对类型注册信息的提供,具体体现在对接口ITypeRegistrationsProvider的实现。该接口定义两个方法GetRegistrations和GetUpdatedRegistrations,两个方法返回的TypeRegistration列表最终用于对UnityContainer的配置,而后者会在配置被改动后被调用。
1: public interface ITypeRegistrationsProvider 2: { 3: IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource); 4: IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource); 5: }
现在ResourceSettings采用如下的方式提供了对ITypeRegistrationsProvider的实现。获取所有基于ResourceProvider的TypeRegistration,如果和配置的默认ResourceProvider名称相同,则将IsDefault设置为true(那么创建的时候就无需指定类型注册名称)。
1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider 2: { 3: //Others... 4: public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource) 5: { 6: var registrations = this.Providers.SelectMany<ResourceProviderDataBase,TypeRegistration>(provider=>provider.GetRegistrations(configurationSource)); 7: foreach (var registration in registrations) 8: { 9: if (registration.Name == this.DefaultProvider) 10: { 11: registration.IsDefault = true; 12: } 13: yield return registration; 14: } 15: } 16: 17: public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource) 18: { 19: return this.GetRegistrations(configurationSource); 20: } 21: }
最后,我们在看看最开始我们给出的程序,你当然知道了这其中的机关在于对ResourceSettings静态方法CongureContainer的调用。
1: static void Main(string[] args) 2: { 3: IUnityContainer container = new UnityContainer(); 4: ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create()); 5: Console.WriteLine(container.Resolve<IResourceProvider>()); 6: Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider")); 7: Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider")); 8: }
该方法定义如下,这其中涉及到两个重要的对象UnityContainerConfigurator和ConfigSectionLocator。ConfigSectionLocator会根据指定配置节名称得到配置节对象,如果配置节类型实现了ITypeRegistrationsProvider接口,会调用GetRegistrations得到所需的TypeRegistration列表,最终借助于UnityContainerConfigurator对具体的UnityContiainer进行类型注册。
1: public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider 2: { 3: //Others... 4: public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource) 5: { 6: UnityContainerConfigurator configurator = new UnityContainerConfigurator(container); 7: configurator.RegisterAll(configurationSource, new ConfigSectionLocator(ConfigSectionName)); 8: } 9: }
调用过ResourceSettings静态方法CongureContainer方法后,UnitiyContainer就具有了DbResourceProvider和XmlResourceProvider的类型注册信息,我们就能通过Resolve方法创建它们。
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句