【问题标题】:Castle Windsor Dependency Injection with custom implementation based on customerCastle Windsor 依赖注入与基于客户的自定义实现
【发布时间】:2016-01-12 22:39:09
【问题描述】:

我们有一种情况,我们使用 Castle Windsor 依赖注入将 IService 注入到 wep api 控制器中,如下所示:

public FooController : ApiController
{
    private IService _service;

    public FooController(IService service)
    {
        _service = service;
    }
}

并像这样注册服务:

 container.Register(Component.For<IService>().ImplementedBy<Service>().LifestyleTransient());

服务类似于:

public Service : IService
{
    public virtual string Logic()
    {
        return "Service Logic";
    }
}

问题是,一些客户的业务逻辑与基础业务逻辑半不同,因此我们需要使用 Service 的另一个实现来满足客户的需求。

因此,如果有 2 个客户(客户 1 和客户 2),客户 1 应使用默认的 Service,但客户 2 应使用名为 Customer2Service 的自定义实现,它将继承自 Service 并根据需要覆盖,如下所示:

public Customer2Service : Service
{
    public override string Logic()
    {
        var response = base.Logic();

        return "Customer 2 Logic " + response;
    }
}

问题

  1. 首先,这似乎是解决客户特定代码这一特殊问题的正确架构吗?
  2. 有没有办法使用依赖注入来解决这个问题? (我们不想要一个巨大的 switch 语句,它只是根据每个操作中的客户名称来选择要使用的服务)

我们的尝试

我们尝试将依赖注入更改为基于属性,如下所示:

public FooController : ApiController
{
    private IService _service;

    public FooController()
    {
    }

    public HttpResponseMessage FooAction(string customer)
    {
        _service = container.Resolve<IService>(customer);

        ...
    }
}

container.Register(Component.For<IService>().ImplementedBy<Service>().LifestyleTransient());
container.Register(Component.For<IService>().ImplementedBy<Customer2Service>().LifestyleTransient().Named("Customer2"));

这样做的问题是,我们必须为每个客户提供定制服务,即使他们不需要,这会变得笨拙。否则解析会抛出一个异常,指出命名依赖项不存在。

【问题讨论】:

  • 看看this question
  • @NightOwl888 我喜欢这个答案,我认为这与 Kenneth 的答案一致,即使用注入的工厂而不是服务。谢谢!

标签: c# dependency-injection castle-windsor


【解决方案1】:

你可以在这里使用工厂:

class ServiceFactory : IServiceFactory
{
    public IService GetService(string customer)
    {
        switch (customer)
        {
            case "special":
                return container.Resolve<IService>("special");
            default:
                return container.Resolve<IService>("default");
        }
    }
}

然后您可以将工厂注入您的控制器并从工厂获得IService。 在 switch 语句中,您只声明特殊情况,所有其他情况都声明为默认子句。

【讨论】:

  • 我试图避免使用 switch 语句,但至少这将它集中在一个地方,而不是在每个动作中。谢谢!
【解决方案2】:

我建议将客户特定的实施保留在不同的项目中。例如,您可以拥有一个名为 CoreServices 的项目,该项目具有基本功能:

namespace CoreServices.Services
{
    public interface IServiceA
    {
        string Test { get; }
    } 

    public interface IServiceB
    {
        string Test { get; }
    }

    public class ServiceA : IServiceA
    {
        public virtual string Test 
        {
            get { return "CoreServices: ServiceA"; } 
        }
    }

    public class ServiceB : IServiceB
    {
        public virtual string Test
        {
            get { return "CoreServices: ServiceB"; }
        }
    }
}

还有另一个项目名称CostomerAServices,其中仅自定义了ServiceA

namespace CustomerAServices.Services
{
    public class ServiceA : CoreServices.Services.ServiceA
    {
        public override string Test
        {
            get { return "CustomerAServices: ServiceA"; }
        }
    }
}

然后,通过运行这段代码,你会得到你需要的:

string customizedAssemblyName = "CustomerAServices"; // Get from config
container.Register(Classes
    .FromAssemblyNamed(customizedAssemblyName)
    .InNamespace(customizedAssemblyName + ".Services")
    .WithServiceAllInterfaces());

container.Register(Classes
   .FromAssemblyNamed("CoreServices")
   .InNamespace("CoreServices.Services")
   .WithServiceAllInterfaces());

首先,您将注册自定义版本和基础版本。所以如果你运行这段代码:

var serviceA = container.Resolve<IServiceA>();
System.Console.WriteLine(serviceA.Test);

var serviceB = container.Resolve<IServiceB>();
System.Console.WriteLine(serviceB.Test);

输出将是:

CustomerAServices: ServiceA
CoreServices: ServiceB

您可以阅读有关自定义温莎注册herehere 的更多信息。

【讨论】:

  • 这是区分服务的不同实现的好方法(我可能会使用它),但是,我正在寻找更多像 Kenneth 的答案。
  • 这真的取决于你的应用程序的大小。如果它很大,那么 Kenneth 的方法可能会变得非常混乱。根据约定解决依赖关系也迫使您拥有良好的代码结构。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多