基于客户的自定义实现的 Castle Windsor 依赖注入
Castle Windsor Dependency Injection with custom implementation based on customer
我们有这样一种情况,我们使用 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 个客户(Customer1 和 Customer2),Customer1 应使用默认 Service
,但 Customer2 应使用名为 Customer2Service
的自定义实现,它将继承自 Service
并根据需要覆盖:
public Customer2Service : Service
{
public override string Logic()
{
var response = base.Logic();
return "Customer 2 Logic " + response;
}
}
问题
- 首先,这看起来是解决客户特定代码这一特定问题的正确架构吗?
- 有没有办法使用依赖注入来解决这个问题? (我们不想要一个巨大的 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"));
问题是,我们必须为每个客户提供定制服务,即使他们不需要,这会变得很笨重。否则解析会抛出一个异常,指出命名依赖不存在。
你可以在这里使用工厂:
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 语句中,您只声明特殊情况,所有其他情况都由 default 子句声明。
我建议在不同的项目中保留客户特定的实现。例如,您可以有一个名为 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
我们有这样一种情况,我们使用 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 个客户(Customer1 和 Customer2),Customer1 应使用默认 Service
,但 Customer2 应使用名为 Customer2Service
的自定义实现,它将继承自 Service
并根据需要覆盖:
public Customer2Service : Service
{
public override string Logic()
{
var response = base.Logic();
return "Customer 2 Logic " + response;
}
}
问题
- 首先,这看起来是解决客户特定代码这一特定问题的正确架构吗?
- 有没有办法使用依赖注入来解决这个问题? (我们不想要一个巨大的 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"));
问题是,我们必须为每个客户提供定制服务,即使他们不需要,这会变得很笨重。否则解析会抛出一个异常,指出命名依赖不存在。
你可以在这里使用工厂:
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 语句中,您只声明特殊情况,所有其他情况都由 default 子句声明。
我建议在不同的项目中保留客户特定的实现。例如,您可以有一个名为 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