Unity动态解析自定义配置

现在只关注与资源的读取,我们将基于不同存储形式的资源读取操作实现在相应的ResourceProovider中,它们实现如下一个简单的IResourceProvider接口。

 

public interface IResourceProvider
{
    object GetObject(string key);
}

 

 

然后我们创建两个具体的ResourceProvider:DbResourceProvider和XmlResourceProvider,它们 分别基于数据库表和XML文件的资源存储形式。DbResourceProvider需要连接数据库,需要引用配置的连接字符串,所以有一个 ConnectionStringName属性;而XmlResourceProvider需要访问具体的XML文件,FileName属性表示文件路 径。

 

[ConfigurationElementType(typeof(DbResourceProviderData))]
public class DbResourceProvider : IResourceProvider
{
    public string ConnectionStringName { get; private set; }
    public DbResourceProvider(string connectionStringName)
    {
        this.ConnectionStringName = connectionStringName;
    }

    public object GetObject(string key)
    { throw new NotImplementedException();}

    public override string ToString()
    {
        return string.Format("DbResourceProvider (ConnectionStringName = {0})", this.ConnectionStringName);
    }
}


[ConfigurationElementType(typeof(XmlResourceProviderData))]
public class XmlResourceProvider : IResourceProvider

public string FileName { get; private set; }

public XmlResourceProvider(string fileName)
{
    this.FileName = fileName;
}

public object GetObject(string key)
{ throw new NotImplementedException();}



public override string ToString()
{
    return string.Format("XmlResourceProvider (FileName = {0})", this.FileName);
}

 

 

具体使用哪个ResourceProvider,通过配置来决定。整个配置定义在<artech.resources>配置节中,该配 置节具有一个<providers>子节点,它定义了一系列ResourceProvider的列表。每个ResourceProvider 配置具有两个相同的属性:Name和Type,以及一些自己专属的配置属性(比如DbResourceProvider的 connectionStringName,XmlResourceProvider的fileName)。至于默认采用哪个Provider,则通过配 置节的defaultProvider属性来决定。在本例中,我们默认采用的是dbProvider。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="artech.resources" type="Artech.ResourceManagement.Configuration.ResourceSettings, Artech.ResourceManagement"/>
  </configSections>
  <artech.resources defaultProvider="dbProvider">
    <providers>
      <add name="xmlProvider" type="Artech.ResourceManagement.XmlResourceProvider, Artech.ResourceManagement" fileName="C:\resources.xml"/>
      <add name="dbProvider" type="Artech.ResourceManagement.DbResourceProvider, Artech.ResourceManagement" connectionStringName="localSqlSvr"/>
    </providers>
  </artech.resources>
</configuration>

现在我们通过创建的UnityContainer来创建相应的ResourceProvider。第一个 Resolve<IResourceProvider>方法没有指定任何参数,表明创建的是配置的默认ResourceProvider(即 DbProvider),后面两个通过指定ResourceProvider的配置名称(xmlProvider和dbProvider)来创建相应的 ResourceProvider。

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create());
    Console.WriteLine(container.Resolve<IResourceProvider>());
    Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider"));
    Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider"));
}

执行结果:

DbResourceProvider (ConnectionStringName = localSqlSvr)
XmlResourceProvider (FileName = C:\resources.xml)
DbResourceProvider (ConnectionStringName = localSqlSvr)

二、整个配置结构的定义

接下来,我们先来看看上面给出的那段配置是如何定义的。我们整个配置定义在如下一个ResourceSettings类型中。ResourceSettings继承自SerializableConfigurationSection,该类型定义在EnterLib(实际上本文介绍的配置与IoC继承的方案来自于EnterLib)中,并实现了接口ITypeRegistrationsProvider。对接口ITypeRegistrationsProvider的实现很重要,我们放在下面一节进行单独介绍。

public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
{
    public const string ConfigSectionName = "artech.resources";

    [ConfigurationProperty("defaultProvider", IsRequired = true)]
    public string DefaultProvider
    { 
        get { return (string)this["defaultProvider"]; } 
    }
 
    [ConfigurationProperty("providers", IsRequired = true)]
    public NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase> Providers
    {
        get { return (NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>)this["providers"]; }
    } 

    public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
    {
       //省略...
    }

    public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource)
    {
        return this.GetRegistrations(configurationSource);
    }

    public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource)
    {
        //省略...
    }
}

配置属性DefaultProvider表示默认的ResourceProvider配置名称,而Providers则表示配置的ResourceProvider列表,这是一个NameTypeConfigurationElementCollection<ResourceProviderDataBase, ResourceProviderDataBase>类型的集合。该集合的每一个元素类型为ResourceProviderDataBase, 它表示所有ResourceProvider配置类型的基类。ResourceProviderDataBase定义如下,它继承自 NameTypeConfigurationElement类型(该类型定义在EnterLib配置系统中)。 ResourceProviderDataBase定义的两个需方法用于类型注册之用,我们在下面一节进行单独介绍。

public class ResourceProviderDataBase : NameTypeConfigurationElement
{
    protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        throw new NotImplementedException();
    }

    public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
    {
        //省略...
    }
}

具体的DbResourceProvider和XmlResourceProvider继承自ResourceProviderDataBase。它们除了定义自己对应的配置属性(connectionStringName和fileName),还需要重写GetCreationExpression这个虚方法。

public class DbResourceProviderData : ResourceProviderDataBase
{
    [ConfigurationProperty("connectionStringName", IsRequired = true)]
    public string ConnectionStringName
    {
        get { return (string)this["connectionStringName"]; }
    }

    protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        //省略...
    }
}


public class XmlResourceProviderData : ResourceProviderDataBase
{
    [ConfigurationProperty("fileName", IsRequired = true)]
    public string FileName
    {
        get { return (string)this["fileName"]; }
    }

    protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        //省略...
    }
}

三、配置项如何提供“类型注册”信息?

所有类型的IoC容器的作用无外乎通过解析注册的各种依赖注入(构造器注入、属性注入和方法注入)通过基类或者接口创建和初始化某个具体类型的实例。Unity可以通过一个特殊的类型来表示依赖注入 信息:TypeRegistration。TypeRegistration定义如下,由于篇幅所限,在这里就不多作介绍了。

public class TypeRegistration
{
    public TypeRegistration(LambdaExpression expression);
    
    public TypeRegistration(LambdaExpression expression, Type serviceType);
    
    public IEnumerable<ParameterValue> ConstructorParameters { get; }

    public Type ImplementationType { get; }

    public IEnumerable<InjectedProperty> InjectedProperties { get; }

    public bool IsDefault { get; set; }

    public bool IsPublicName { get; set; }

    public LambdaExpression LambdaExpression { get; }

    public TypeRegistrationLifetime Lifetime { get; set; }

    public string Name { get; set; }

    public NewExpression NewExpressionBody { get; }

    public Type ServiceType { get; }

    public static string DefaultName<TServiceType>();

    public static string DefaultName(Type serviceType);
}

现在我们的目的是让UnityContainer来解析定义在ResourceSettings这个配置类型中的“注入信息”,那么就需要ResourceSettings对象能够提供它一个完备的TypeRegistration列表,这些列表帮助UnityContainer进行正确的实例创建或者获取决策。现在我们就来介绍ResourceSettings如果为UnityContainer提供类型注册信息的,现在我们将关注点放在上面给出的代码中的省略部分。先来看看ResourceProviderDataBase的定义。

public class ResourceProviderDataBase : NameTypeConfigurationElement
{
    protected virtual Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        throw new NotImplementedException();
    }

    public virtual IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
    {
        Expression<Func<IResourceProvider>> creationExpression = GetCreationExpression(configurationSource);
        TypeRegistration providerTypeRegistration = new TypeRegistration<IResourceProvider>(creationExpression)
        {
            Name = this.Name,
            IsPublicName = true,
            Lifetime = TypeRegistrationLifetime.Singleton
        };
        yield return providerTypeRegistration;
    }
}

需方法GetRegistrationsIEnumerable<TypeRegistration>类型的方式获取所有基于该配置元素的类型注册信息,这里提供默认的实现——基于具体ResourceProvider类型的类型注册。在这里我们通过一个Expression<Func<IResourceProvider>>对象作为参数创建TypeRegistration对象,而传入的表达式用于具体ResourceProvider的创建。该Expression<Func<IResourceProvider>>对象通过需方法GetCreationExpression方法获取,所有具体的子类需要重写这个方法。(P.S. 为了避免对ResourceProvider的频繁创建,我通过Lifetime将其设置成Singleton形式)

对于继承自ResourceProviderDataBase的DbResourceProviderData和 XmlResourceProviderData重写了GetCreationExpression方法,实现了对DbResourceProvider 和XmlResourceProvider的创建。

public class DbResourceProviderData : ResourceProviderDataBase
{
    //Others
    protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        return () => new DbResourceProvider(this.ConnectionStringName);
    }
}

public class XmlResourceProviderData : ResourceProviderDataBase
{
    //Others
    protected override Expression<Func<IResourceProvider>> GetCreationExpression(IConfigurationSource configurationSource)
    {
        return () => new XmlResourceProvider(this.FileName);
    }
}

我们说ResourceSettings实现了对类型注册信息的提供,具体体现在对接口ITypeRegistrationsProvider的实现。该接口定义两个方法GetRegistrations和GetUpdatedRegistrations,两个方法返回的TypeRegistration列表最终用于对UnityContainer的配置,而后者会在配置被改动后被调用。

public interface ITypeRegistrationsProvider
{
    IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource);
    IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource);
}

现在ResourceSettings采用如下的方式提供了对ITypeRegistrationsProvider的实现。获取所有基于 ResourceProvider的TypeRegistration,如果和配置的默认ResourceProvider名称相同,则将IsDefault设置为true(那么创建的时候就无需指定类型注册名称)。

public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
{
    //Others...
    public IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
    {
        var registrations = this.Providers.SelectMany<ResourceProviderDataBase,TypeRegistration>(provider=>provider.GetRegistrations(configurationSource));
        foreach (var registration in registrations)
        {
            if (registration.Name == this.DefaultProvider)
            {
                registration.IsDefault = true;
            }
            yield return registration;
        }
    } 

    public IEnumerable<TypeRegistration> GetUpdatedRegistrations(IConfigurationSource configurationSource)
    {
        return this.GetRegistrations(configurationSource);
    }
}

四、如何为UnityContainer进行“类型注册”

最后,我们在看看最开始我们给出的程序,你当然知道了这其中的机关在于对ResourceSettings静态方法CongureContainer的调用。

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    ResourceSettings.ConfigureContainer(container, ConfigurationSourceFactory.Create());
    Console.WriteLine(container.Resolve<IResourceProvider>());
    Console.WriteLine(container.Resolve<IResourceProvider>("xmlProvider"));
    Console.WriteLine(container.Resolve<IResourceProvider>("dbProvider"));
}

该方法定义如下,这其中涉及到两个重要的对象UnityContainerConfiguratorConfigSectionLocator。 ConfigSectionLocator会根据指定配置节名称得到配置节对象,如果配置节类型实现了 ITypeRegistrationsProvider接口,会调用GetRegistrations得到所需的TypeRegistration列表, 最终借助于UnityContainerConfigurator对具体的UnityContiainer进行类型注册。

public class ResourceSettings : SerializableConfigurationSection, ITypeRegistrationsProvider
{
    //Others...
    public static void ConfigureContainer(IUnityContainer container, IConfigurationSource configurationSource)
    {
        UnityContainerConfigurator configurator = new UnityContainerConfigurator(container);
        configurator.RegisterAll(configurationSource, new ConfigSectionLocator(ConfigSectionName));
    }
}

调用过ResourceSettings静态方法CongureContainer方法后,UnitiyContainer就具有了DbResourceProvider和XmlResourceProvider的类型注册信息,我们就能通过Resolve方法创建它们。

copspoortRest -
共有0个回答