【问题标题】:Hot-swapping dependencies in UnityUnity 中的热交换依赖项
【发布时间】:2015-12-17 18:43:36
【问题描述】:

我刚开始使用 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 从存储库中检索实际数据时,首先它来自“prodRepository”,这是可以理解的,因为这就是它在代码中的初始化方式。从浏览器将其切换到“testRepository”后,数据按预期来自测试存储库。当我将其切换回 prodRepository 时,API 会不断向我发送来自测试存储库的数据。 我在控制器中看到 GlobalConfiguration.Configuration .DependencyResolver 按预期将容器和注册更改为 URL 查询中指定的内容,但它似乎只更改了一次配置,然后保持在该配置。

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

【问题讨论】:

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


    【解决方案1】:

    看起来你在很多方面都出了问题:

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

    这里是#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

    统一注册

    这里我们使用 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 的单个问题所能回答的要多。

    【讨论】:

    • NightOwl888,谢谢你的帖子。您将如何具体实现#4(在运行时切换服务)?一些代码会有所帮助,谢谢。
    • 甜心,感谢您对未来研究的所有指点,非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多