Unity中的热交换依赖项

时间:2015-12-17 18:43:37

标签: c# dependency-injection inversion-of-control unity-container ioc-container

我刚刚开始使用Unity IOC,希望有人能提供帮助。我需要能够在运行时切换Unity中的依赖项。我有两个容器,分别用于生产和开发/测试环境," prodRepository"和" testRepository"在web.config中定义如下:

    <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
        <alias alias="TestDataService" type="MyApp.API.Data.TestDataRepository.TestDataService, MyApp.API.Data" />
        <alias alias="ProdDataService" type="MyApp.API.Data.ProdDataRepository.ProdDataService, MyApp.API.Data" />
        <assembly name="MyApp.API.Data" />
        <container name="testRepository">
            <register type="MyApp.API.Data.IDataService"  mapTo="TestDataService">
                <lifetime type="hierarchical" />
            </register>
        </container>
        <container name="prodRepository">
            <register type="MyApp.API.Data.IDataService"  mapTo="ProdDataService">
                <lifetime type="hierarchical" />
            </register>
        </container>
    </unity>

在WebApiConfig类中,Unit配置如下

public static void Register(HttpConfiguration config)
{

    config.DependencyResolver = RegisterUnity("prodRepository");
  //... api configuration ommitted for illustration
}

public static IDependencyResolver RegisterUnity(string containerName)
{
    var container = new UnityContainer();
    container.LoadConfiguration(containerName);
    return new UnityResolver(container);
}

为了测试,我创建了一个简单的控制器和操作来切换配置:

[HttpGet]
public IHttpActionResult SwitchResolver(string rName)
{

    GlobalConfiguration.Configuration
        .DependencyResolver =   WebApiConfig.RegisterUnity(rName);
    return Ok();
}

我从网络浏览器中调用它: http://localhost/MyApp/api/Test/SwitchResolver?rName=prodRepository

当我尝试通过API从存储库中检索实际数据时,首先它来自&#34; prodRepository&#34;,可以理解,因为它是如何在代码中初始化的。我把它切换到&#34; testRepository&#34;从浏览器中,数据来自测试仓库,如预期的那样。当我将其切换回prodRepository时,API会不断向我发送测试仓库中的数据。 我在控制器中看到GlobalConfiguration.Configuration .DependencyResolver按预期将容器和注册更改为URL查询中指定的容器和注册,但它似乎只更改配置一次然后保持在该配置。

好的,所以这个邪恶的计划就是我想出来的,但由于我是新手,我可能完全是错误的方向。 我需要能够在运行时动态指定要使用哪个容器,希望无需重新加载API。以上代码是否有意义或您建议什么?

1 个答案:

答案 0 :(得分:2)

看起来你在很多方面都出错:

  1. 使用XML配置DI容器被认为是一种过时的方法。
  2. 您真的想要从生产环境访问测试数据,反之亦然?通常通过配置设置选择一个环境,并且在部署到每个环境时更改设置本身。在这种情况下,在应用程序启动时仅加载数据服务一次是有意义的。
  3. 如果#2的答案为否,那么轻松可靠地完成工作的一种方法是在部署期间使用web.config transforms
  4. 如果对#2的回答是肯定的,你可以使用strategy pattern解决这个问题,它允许你在启动时创建所有数据服务,并在运行时在它们之间切换。
  5. 以下是#4的例子:

      

    注意: WebApi是无状态的。请求结束后,它不会在服务器上存储任何内容。此外,如果您的WebApi客户端不是浏览器,您可能无法使用session state等技术来存储您从一个请求访问哪个数据提供者,因为这取决于cookie。

         

    因此,采取SwitchResolver行动可能是荒谬的。您应该在每个请求上提供存储库,或者具有可以使用每个请求的参数覆盖的默认存储库。

    接口

    public interface IDataService
    {
        void DoSomething();
        bool AppliesTo(string provider);
    }
    
    public interface IDataServiceStrategy
    {
        void DoSomething(string provider);
    }
    

    数据服务

    public class TestDataService : IDataService
    {
        public void DoSomething()
        {
            // Implementation
        }
    
        public bool AppliesTo(string provider)
        {
            return provider.Equals("testRepository");
        }
    }
    
    public class ProdDataService : IDataService
    {
        public void DoSomething()
        {
            // Implementation
        }
    
        public bool AppliesTo(string provider)
        {
            return provider.Equals("prodRepository");
        }
    }
    

    策略

    这是完成所有繁重任务的班级。

    有一个GetDataService方法,它根据传入的字符串返回所选服务。请注意,您也可以公开此方法,以便将IDataService的实例返回给您的控制器,这样您就不必进行DoSomething的两次实现。

    public class DataServiceStrategy
    {
        private readonly IDataService[] dataServices;
    
        public DataServiceStrategy(IDataService[] dataServices)
        {
            if (dataServices == null)
                throw new ArgumentNullException("dataServices");
            this.dataServices = dataServices;
        }
    
        public void DoSomething(string provider)
        {
            var dataService = this.GetDataService(provider);
            dataService.DoSomething();
        }
    
        private IDataService GetDataService(string provider)
        {
            var dataService = this.dataServices.Where(ds => ds.AppliesTo(provider));
            if (dataService == null)
            {
                // Note: you could alternatively use a default provider here
                // by passing another parameter through the constructor
                throw new InvalidOperationException("Provider '" + provider + "' not registered.");
            }
            return dataService;
        }
    }
    

    请参阅这些替代实施以获得灵感:

    Best way to use StructureMap to implement Strategy pattern

    Factory method with DI and Ioc

    Unity注册

    这里我们使用container extension而不是XML配置向Unity注册服务。

    您还应确保使用correct way to register Unity with WebApi as per MSDN

    public static IDependencyResolver RegisterUnity(string containerName)
    {
        var container = new UnityContainer();
        container.AddNewExtension<MyContainerExtension>();
        return new UnityResolver(container);
    }
    
    public class MyContainerExtension
        : UnityContainerExtension
    {
        protected override void Initialize()
        {
            // Register data services
    
            // Important: In Unity you must give types a name in order to resolve an array of types
            this.Container.RegisterType<IDataService, TestDataService>("TestDataService");
            this.Container.RegisterType<IDataService, ProdDataService>("ProdDataService");
    
            // Register strategy
    
            this.Container.RegisterType<IDataServiceStrategy, DataServiceStrategy>(
                new InjectionConstructor(new ResolvedParameter<IDataService[]>()));
        }
    }
    

    用法

    public class SomeController : ApiController
    {
        private readonly IDataServiceStrategy dataServiceStrategy;
    
        public SomeController(IDataServiceStrategy dataServiceStrategy)
        {
            if (dataServiceStrategy == null)
                throw new ArgumentNullException("dataServiceStrategy");
            this.dataServiceStrategy = dataServiceStrategy;
        }
    
        // Valid values for rName are "prodRepository" or "testRepository"
        [HttpGet]
        public IHttpActionResult DoSomething(string rName)
        {
            this.dataServiceStrategy.DoSomething(rName);
            return Ok();
        }
    }
    

    我强烈建议您阅读Mark Seemann撰写的Dependency Injection in .NET一书。它将帮助您走上正确的道路,并帮助您为应用程序做出最佳选择,因为它们适用于DI,这比我在SO上的单个问题上可以回答的要多。