Unity 中的热交换依赖项

Hot-swapping dependencies in Unity

我刚开始使用 Unity IOC,希望有人能提供帮助。我需要能够在 运行 时间切换 Unity 中的依赖项。我有两个容器分别用于生产环境和 dev/test 环境,"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 class 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。上面的代码是否有意义或者你有什么建议?

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

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

这是#4 的示例:

NOTE: WebApi is stateless. It doesn't store anything on the server after the request has ended. Furthermore, if your WebApi client is not a browser, you may not be able to use techniques such as session state to store which data provider you are accessing from one request to the next because this depends on cookies.

Therefore, having a SwitchResolver action is probably nonsensical. You should provide the repository on each request or otherwise have a default repository that can be overridden with a parameter per request.

接口

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");
    }
}

策略

这是完成所有繁重工作的 class。

有一个 GetDataService 方法可以根据传入的字符串 return 选择服务。请注意,您也可以使此方法 public 以便 return 一个 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

统一注册

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