【问题标题】:Dependency injection in web apiweb api中的依赖注入
【发布时间】:2019-04-05 00:15:57
【问题描述】:

我有一个关于构建依赖关系的正确方法的问题。

假设你有一个 web api 控制器,它有一个带有两个参数的方法:id 和 bar。 控制器需要根据 'id' 值读取特定配置,并向应用了该配置的不同服务发出请求。

    public class FooController : ApiController
    {
        private readonly IConfigurationProvider _configurationProvider;
        private readonly IService _service;

        public FooController(IConfigurationProvider configurationProvider, IService service)
       {
             _configurationProvider = configurationProvider;
             _service = service;
       }

        public IHttpActionResult Bar(int id, int bar)
        {
            var configuration = _configurationProvider.GetConfiguration(id);
            _service.Configure(configuration);
            var barResult = _service.Bar(bar);

            return Ok(barResult);
        }
}

我不太确定我在依赖注入方面做得对:

  • 服务在构造函数中初始化,但配置 在执行请求之前不知道。服务的客户 可能不会调用配置,它可能会导致问题(会某种 Builder 模式在这里工作?)。
  • 服务本身是否应该负责其配置加载?

从依赖注入的角度来看,这个例子还有其他问题吗?

谢谢。

【问题讨论】:

  • 您的依赖结构似乎有误。控制器依赖于 IConfigurationProvider,但实际上不做任何事情,只是将检索到的配置传递给它也依赖的服务。此外,依赖项不应该是基础设施代码,IConfigurationProvider 看起来就像基础设施代码渗入您的控制器。所以真正应该发生的是,这些基础设施问题应该在您的组件注册代码中处理。
  • 了解您正在使用的 IoC 容器也很好。

标签: asp.net-web-api dependency-injection dependencies


【解决方案1】:

您可以尝试使用工厂类(构建器类可能是错误的)

public class ServiceFactory : IServiceFactory
{
    private readonly Dictionary<int, IService> _services = new Dictionary<int, IService>();
    private readonly IConfigurationProvider _configurationProvider;

    public ServiceFactory(IConfigurationProvider configurationProvider)
    {
        _configurationProvider = configurationProvider;
    }

    public IService GetService(int id)
    {
        if (!_services.ContainsKey(id))
        {
            var config = _configurationProvider.GetConfiguration(id);
            var service = new Service(config);
            _services.Add(id, service);
        }

        return _services[id];
    }
}

这将创建一个服务实例,并在工厂类的生命周期内保持对它的引用,因此它只需要创建一次。然后注册 ServiceFactory 并将其注入到你的控制器中。

public class FooController : ApiController
{
    private readonly IServiceFactory _serviceFactory;

    public FooController(IServiceFactory serviceFactory)
    {
        _serviceFactory = serviceFactory;
    }

    public IHttpActionResult Bar(int id, int bar)
    {
        var service = _serviceFactory.GetService(id);
        var barResult = service.Bar(bar);

        return Ok(barResult);
    }
}

现在你的控制器仍然是可测试的,你不必担心 Configure() 被调用,因为 'id' 是对服务构造函数的依赖。

附:不尊重@Ahmed Sherien,但不要使用 Unity,它又旧又慢!!! ;-)

【讨论】:

    【解决方案2】:

    在这些情况下,我会使用键控解析...如果您使用统一,则类似这样:

    IUnityContainer container = new UnityContainer();
    container.RegisterType<IConfiguration, ConfigurationType1>("Type1");
    container.RegisterType<IConfiguration, ConfigurationType2>("Type2");
    
    
    IConfiguration cfg1 = container.Resolve<IConfiguration>("Type1");  // return ConfigurationType1 object
    IConfiguration cfg2 = container.Resolve<IConfiguration>("Type2"); // return ConfigurationType2 object
    

    在您的情况下,实现将如下所示:

    public class FooController : ApiController
    {
        private readonly IService _service;
    
        public FooController(IService service)
        {
            _service = service;
        }
    
        public IHttpActionResult Bar(int id, int bar)
        {
            var configuration = container.Resolve<IConfiguration>(id.ToString());
            _service.Configure(configuration);
            var barResult = _service.Bar(bar);
    
            return Ok(barResult);
        }
    }
    

    【讨论】:

    • 直接在控制器内部使用容器可能是个坏主意,首先现在你的控制器是不可测试的(如果你想测试你的控制器)它实际上让我想起了服务定位器模式太可怕了,其次,您的代码现在可以与您选择的 IoC 容器紧密耦合。在您的代码示例中,“容器”变量来自哪里?
    • @matt_lethargic 我会在全局变量中定义它,但显然这也是个坏主意......
    • 是的,全局变量仍然很糟糕,仍然使您的控制器无法测试
    猜你喜欢
    • 1970-01-01
    • 2017-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-19
    • 1970-01-01
    • 2018-03-24
    相关资源
    最近更新 更多