Unity中的热交换依赖项

本文关键字:依赖 热交换 Unity | 更新日期: 2023-09-27 18:27:17

我刚从Unity IOC开始,希望有人能帮忙。我需要能够在运行时切换Unity中的依赖项。我有两个容器,分别用于生产和开发/测试环境,"prodRepository"answers"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类中,单元配置如下

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"后,数据如预期的那样来自测试repo。当我将其切换回prodRepository时,API会不断向我发送来自测试回购的数据。我在控制器中看到GlobalConfiguration.Configuration.DependencyResolver按预期将容器和注册更改为URL查询中指定的容器和注册,但它似乎只更改了一次配置,然后保持该配置。

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

Unity中的热交换依赖项

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

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

下面是#4的一个例子:

注意:WebApi是无状态的。请求结束后,它不会在服务器上存储任何内容。此外,如果您的WebApi客户端不是浏览器,您可能无法使用会话状态等技术来存储您从一个请求到下一个请求访问的数据提供商,因为这取决于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;
    }
}

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

使用StructureMap实现策略模式的最佳方法

DI和Ioc 的工厂方法

Unity注册

在这里,我们使用容器扩展而不是XML配置向Unity注册服务。

您还应该确保使用正确的方式按照MSDN向WebApi注册Unity。

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的《.NET中的依赖注入》一书。它将帮助您走上正确的道路,并帮助您在应用程序应用于DI时为其做出最佳选择,这比我在SO上回答一个问题所能回答的还要多。