我的应用程序中有三种类型的用户,例如Type1, Type2 and Type3
。
然后,我想为每种类型创建一个服务实现,假设我有一个获取照片的服务,我将拥有三个服务:Type1PhotosService, Type2PhotosService and Type3PhotosService
,每个服务都实现IPhotosService
。
在网络api中,我将注入IPhotosService
:
IPhotosService _service;
public PhotosController(IPhotosService service){
_service = service;
}
Web api使用带声明的令牌身份验证。因此,对于每个用户,我要实现的目标是根据他的声明:type1 or type2 or type3
,将自动注入正确的服务实现,而不是在startup
文件中注入单个服务。
我要避免的是提供一项服务,并带有一堆switch
和if
语句,以根据用户类型和他所扮演的角色返回正确的数据。
编辑:
一些评论想知道这三种实现的意义是什么,所以这里有更多细节可以使它更加有意义。
该服务是一个求职者服务,该应用程序具有三个不同的配置文件:candidate, employer and administration
。这些配置文件中的每一个都需要适当的实现。因此,与其在同一个服务中使用三种方法GetCandidateJobs, GetEmployerJobs and GetAdministrationJobs
并打开用户类型,不如我对每种配置文件类型使用一个实现,然后根据配置文件类型使用正确的实现。
答案 0 :(得分:2)
我将回避在这种情况下是否有意义的问题,并尝试按照要求回答问题:
.NET Core的IoC容器不适用于这种情况。 (他们在其文档中承认这一点。)您可以通过添加另一个IoC容器(如Windsor)来解决此问题。
该实现最终看起来比我想要的要复杂得多,但是一旦您完成设置,就可以了,而且可以使用Windsor的功能。我将提供不包含温莎的另一个答案。我必须做所有这些工作才能看到我可能更喜欢其他方法。
在您的项目中,添加Castle.Windsor.MsDependencyInjection NuGet程序包。
为了进行测试,我添加了一些接口和实现:
public interface ICustomService { }
public interface IRegisteredWithServiceCollection { }
public class CustomServiceOne : ICustomService { }
public class CustomServiceTwo : ICustomService { }
public class CustomServiceThree : ICustomService { }
public class RegisteredWithServiceCollection : IRegisteredWithServiceCollection { }
目的是创建一个工厂,该工厂将使用一些运行时输入来选择并返回ICustomService
的实现。
这是一个将用作工厂的界面。这就是我们可以注入到类中并在运行时调用以实现ICustomService
的实现:
public interface ICustomServiceFactory
{
ICustomService Create(string input);
}
下一个是将配置IWindsorContainer
来解决依赖关系的类:
public class WindsorConfiguration : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<ICustomService, CustomServiceOne>().Named("TypeOne"),
Component.For<ICustomService, CustomServiceTwo>().Named("TypeTwo"),
Component.For<ICustomService, CustomServiceThree>().Named("TypeThree"),
Component.For<ICustomService, CustomServiceOne>().IsDefault(),
Component.For<ICustomServiceFactory>().AsFactory(new CustomServiceSelector())
);
}
}
public class CustomServiceSelector : DefaultTypedFactoryComponentSelector
{
public CustomServiceSelector()
: base(fallbackToResolveByTypeIfNameNotFound: true) { }
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
return (string) arguments[0];
}
}
这是这里发生的事情:
TypedFactoryFacility
将使我们能够使用温莎的类型化工厂。它将为我们创建工厂接口的实现。 ICustomService
的三种实现。因为我们要注册多个实现,所以每个实现都必须有一个名称。解析ICustomService
时,我们可以指定一个名称,它将根据该字符串解析类型。ICustomService
的另一种实现而没有名称。如果我们尝试使用无法识别的名称进行解析,这将使我们能够解析默认实现。 (某些替代方法只是引发异常,或者返回ICustomService
的“空”实例,或者创建诸如UnknownCustomService
之类的引发异常的类。)Component.For<ICustomServiceFactory>().AsFactory(new CustomServiceSelector())
告诉容器创建代理类以实现ICustomServiceFactory
。 (有关their documentation的更多信息。)CustomServiceSelector
是将参数传递给工厂的Create
方法并返回用于选择组件的组件名称(TypeOne,TypeTwo等)的方法。在这种情况下,我们希望传递给工厂的参数与我们使用的注册名称相同。但是我们可以用其他逻辑代替它。我们的工厂甚至可以接受其他类型的参数,我们可以检查并确定要返回的字符串。现在,在StartUp中,修改ConfigureServices
以返回IServiceProvider
而不是void
,并创建一个IServiceProvider
,将直接在IServiceCollection
中注册的服务与那些在Windsor容器中注册:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var container = new WindsorContainer();
container.Install(new WindsorConfiguration());
return WindsorRegistrationHelper.CreateServiceProvider(container, services);
}
container.Install(new WindsorConfiguration())
允许WindsorConfiguration
配置我们的容器。我们可以直接使用这种方法配置容器,但这是保持容器配置井井有条的好方法。我们可以创建许多IWindsorInstaller
实现或我们自己的自定义类来配置Windsor容器。
WindsorRegistrationHelper.CreateServiceProvider(container, services)
创建使用IServiceProvider
和container
的{{1}}。
如果不先查找,我不会发布所有这些内容。这是一些NUnit测试。 (我通常会为DI配置编写一些基本测试。)
安装程序将创建一个services
,类似于应用程序启动时会发生的情况。它创建一个容器并应用IServiceProvider
。我还将直接在WindsorConfiguration
上注册服务,以确保两者可以很好地配合使用。然后,我将两者合并为ServiceCollection
。
然后我要从IServiceProvider
解析一个ICustomerServiceFactory
并验证它为每个输入字符串返回正确的IServiceProvider
实现,包括当字符串不是字符串时的回退识别的依赖项名称。
我还在验证直接向ICustomService
注册的服务是否已解决。
ServiceCollection
现在我已经弄清楚了,如果我真的需要这种功能,我可能会使用它。如果您想同时使用Windsor和.NET Core ,并且第一次看到它是抽象工厂实现,那看起来会更糟。 Here's another article,并提供了有关Windsor抽象工厂的更多信息,而没有关于.NET Core的所有干扰。
答案 1 :(得分:2)
我将在这里走出去,说为此目的利用依赖注入的尝试是次优的。通常,这将由Factory模式处理,该模式使用可怕的if
和switch
语句生成服务实现。一个简单的例子是:
public interface IPhotoService {
Photo CreatePhoto(params);
}
public class PhotoServiceFactory {
private readonly IPhotoService _type1;
private readonly IPhotoService _type2;
private readonly IPhotoService _type3;
public PhotoServiceFactory(IDependency1 d1, IDependency2 d2, ...etc) {
_type1 = new ConcreteServiceA(d1);
_type2 = new ConcreteServiceB(d2);
_type3 = new ConcreteServiceC(etc);
}
public IPhotoService Create(User user) {
switch(user.Claim) {
case ClaimEnum.Type1:
return _type1;
case ClaimEnum.Type2:
return _type2;
case ClaimEnum.Type3:
return _type3;
default:
throw new NotImplementedException
}
}
}
然后在您的控制器中:
public class PhotosController {
IPhotoServiceFactory _factory;
public PhotosController(IPhotoServiceFactory factory){
_factory = factory;
}
public IHttpActionResult GetPhoto() {
var photoServiceToUse = _factory.Create(User);
var photo = photoServiceToUse.CreatePhoto(params);
return Ok(photo);
}
}
或者仅将具体类用作构造函数中的参数,并遵循与上述类似的逻辑。
答案 2 :(得分:0)
与配置应用程序以使用另一个IoC容器然后配置该容器相比,这里的方法要容易得多。与Windsor一起完成此工作之后,此解决方案似乎容易得多。
如果您可以使用每个服务实现的单例实例,则此方法最简单。
我们将从一个接口,一些实现以及我们可以注入的工厂开始,该工厂将根据某些输入返回在运行时选择的实现。
public interface ICustomService { }
public class CustomServiceOne : ICustomService { }
public class CustomServiceTwo : ICustomService { }
public class CustomServiceThree : ICustomService { }
public interface ICustomServiceFactory
{
ICustomService Create(string input);
}
这是工厂的一个非常原始的实现。 (既不使用字符串常量,也没有使用字符串常量。)
public class CustomServiceFactory : ICustomServiceFactory
{
private readonly Dictionary<string, ICustomService> _services
= new Dictionary<string, ICustomService>(StringComparer.OrdinalIgnoreCase);
public CustomServiceFactory(IServiceProvider serviceProvider)
{
_services.Add("TypeOne", serviceProvider.GetService<CustomServiceOne>());
_services.Add("TypeTwo", serviceProvider.GetService<CustomServiceTwo>());
_services.Add("TypeThree", serviceProvider.GetService<CustomServiceThree>());
}
public ICustomService Create(string input)
{
return _services.ContainsKey(input) ? _services[input] : _services["TypeOne"];
}
}
这假定您已经向CustomServiceOne
注册了CustomServiceTwo
,IServiceCollection
等。它们不会被注册为接口实现,因为这不是我们解决它们的方式。此类将简单地解决每个问题并将它们放入字典中,以便您可以按名称检索它们。
在这种情况下,factory方法采用字符串,但是您可以检查任何类型或多个参数以确定要返回的实现。甚至使用字符串作为字典键也是任意的。并且,作为示例,我提供了后备行为以返回一些默认实现。如果您无法确定要返回的正确实现,则抛出异常可能更有意义。
根据您的需求,另一种选择是在工厂要求时解决该实现。我会尽可能地使大多数类保持无状态,以便我可以解析和重用单个实例。
要在启动时向IServiceCollection
注册工厂,我们可以这样做:
services.AddSingleton<ICustomServiceFactory>(provider =>
new CustomServiceFactory(provider));
IServiceProvider
将在工厂解决后注入工厂,然后工厂将使用它来解决服务。
这是相应的单元测试。测试方法与Windsor答案中使用的方法相同,后者“证明”我们可以用另一种工厂透明地替换一个工厂实现,并在组成根中更改其他内容而不会破坏内容。
public class Tests
{
private IServiceProvider _serviceProvider;
[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddSingleton<CustomServiceOne>();
services.AddSingleton<CustomServiceTwo>();
services.AddSingleton<CustomServiceThree>();
services.AddSingleton<ICustomServiceFactory>(provider =>
new CustomServiceFactory(provider));
_serviceProvider = services.BuildServiceProvider();
}
[TestCase("TypeOne", typeof(CustomServiceOne))]
[TestCase("TypeTwo", typeof(CustomServiceTwo))]
[TestCase("TYPEThree", typeof(CustomServiceThree))]
[TestCase("unknown", typeof(CustomServiceOne))]
public void FactoryReturnsExpectedService(string input, Type expectedType)
{
var factory = _serviceProvider.GetService<ICustomServiceFactory>();
var service = factory.Create(input);
Assert.IsInstanceOf(expectedType, service);
}
}
就像在Windsor示例中一样,编写此代码是为了避免对合成根目录之外的容器进行任何引用。如果类依赖于ICustomServiceFactory
和ICustomService
,则可以在此实现,Windsor实现或工厂的任何其他实现之间切换。
答案 3 :(得分:0)
这是我在asp.net核心控制台应用程序内部创建的一种解决方案。
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
namespace CreationalPattern
{
class Program
{
static void Main(string[] args)
{
// Add dependency into service collection
var services = new ServiceCollection()
.AddTransient<FordFigoFactory>()
.AddTransient<AudiQ7Factory>();
/* Create CarServiceFactory as singleton because it can be used across the application more frequently*/
services.AddSingleton<ICarServiceFactory>(provider => new CarServiceFactory(provider));
// create a service provider from the service collection
var serviceProvider = services.BuildServiceProvider();
/* instantiate car*/
var factory = serviceProvider.GetService<ICarServiceFactory>();
var audiCar = factory.Create("audi").CreateACar("Blue");
Console.Read();
}
}
public interface ICarServiceFactory
{
ICreateCars Create(string input);
}
public class CarServiceFactory : ICarServiceFactory
{
private readonly Dictionary<string, ICreateCars> _services
= new Dictionary<string, ICreateCars>(StringComparer.OrdinalIgnoreCase);
public CarServiceFactory(IServiceProvider serviceProvider)
{
_services.Add("ford", serviceProvider.GetService<FordFigoFactory>());
_services.Add("audi", serviceProvider.GetService<AudiQ7Factory>());
}
public ICreateCars Create(string input)
{
Console.WriteLine(input + " car is created.");
return _services.ContainsKey(input) ? _services[input] : _services["ford"];
}
}
public interface ICreateCars
{
Car CreateACar(string color);
}
public class FordFigoFactory : ICreateCars
{
public Car CreateACar(string color)
{
Console.WriteLine("FordFigo car is created with color:" + color);
return new Fordigo { Color = color};
}
}
public class AudiQ7Factory : ICreateCars
{
public Car CreateACar(string color)
{
Console.WriteLine("AudiQ7 car is created with color:" + color);
return new AudiQ7 { Color = color };
}
}
public abstract class Car
{
public string Model { get; set; }
public string Color { get; set; }
public string Company { get; set; }
}
public class Fordigo : Car
{
public Fordigo()
{
Model = "Figo";
Company = "Ford";
}
}
public class AudiQ7 : Car
{
public AudiQ7()
{
Model = "Audi";
Company = "Q7";
}
}
}
说明: 为了更好地理解,请尝试从下至上阅读该程序。我们分为3个部分:
在此依赖注入中,工厂类FordFigoFactory和AudiQ7Factory被注册为瞬态。还有Singleton用于CarServiceFactory。