bad*_*oui 8 c# asp.net asp.net-mvc dependency-injection asp.net-core
我的应用程序中有三种类型的用户,比方说Type1, Type2 and Type3。然后我想为每种类型创建一个服务实现,假设我有一个获取照片的服务,我将拥有三个服务:Type1PhotosService, Type2PhotosService and Type3PhotosService,每个服务都实现IPhotosService.
在 web api 中,我会注入IPhotosService:
IPhotosService _service;
public PhotosController(IPhotosService service){
_service = service;
}
Run Code Online (Sandbox Code Playgroud)
Web api 使用带有声明的令牌身份验证。所以我想要实现的是,对于每个用户,根据他的声明:type1 or type2 or type3,服务的正确实现将被自动注入,而不是在startup文件中注入单个服务。我想避免的是,有一个服务,有一堆switchandif语句,根据用户类型和他拥有的角色返回正确的数据。
编辑:有些评论想知道三个实现的意义是什么,所以这里有更多细节让它更有意义。该服务是一个求职服务,该应用程序具有三个不同的配置文件:candidate, employer and administration. 这些配置文件中的每一个都需要适当的实现。因此,与其GetCandidateJobs, GetEmployerJobs and GetAdministrationJobs在同一服务中使用三种方法并打开用户类型,我更喜欢每个配置文件类型有一个实现,然后根据配置文件类型使用正确的实现。
我将回避有关在这种情况下这是否有意义的问题,而只是尝试按要求回答问题:
.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 { }
Run Code Online (Sandbox Code Playgroud)
目的是创建一个工厂,该工厂将选择并返回ICustomService使用某些运行时输入的实现。
这是一个用作工厂的接口。这是我们可以注入到一个类中并在运行时调用以获取以下内容的实现ICustomService:
public interface ICustomServiceFactory
{
ICustomService Create(string input);
}
Run Code Online (Sandbox Code Playgroud)
接下来是一个类,它将配置一个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];
}
}
Run Code Online (Sandbox Code Playgroud)
这是这里发生的事情:
TypedFactoryFacility将使我们能够使用 Windsor 的类型化工厂。它将为我们创建工厂接口的实现。ICustomService. 因为我们注册了多个实现,每个实现都必须有一个名称。当我们解析时,ICustomService我们可以指定一个名称,它会根据该字符串解析类型。ICustomService没有名字的实现。如果我们尝试使用无法识别的名称进行解析,这将使我们能够解析默认实现。(一些替代方法只是抛出异常,或者返回一个“空”实例ICustomService或创建一个类似UnknownCustomService抛出异常的类。)Component.For<ICustomServiceFactory>().AsFactory(new CustomServiceSelector())告诉容器创建一个代理类来实现ICustomServiceFactory。(更多关于他们的文档。)CustomServiceSelector是接受传递给工厂Create方法的参数并返回将用于选择组件的组件名称(TypeOne、TypeTwo 等)。在这种情况下,我们期望传递给工厂的参数与我们使用的注册名称相同。但是我们可以用其他逻辑代替它。我们的工厂甚至可以接受其他类型的参数,我们可以检查并确定要返回的字符串。现在,在 StartUp 中,修改ConfigureServices为 returnIServiceProvider而不是void并创建一个IServiceProvider将直接注册的服务IServiceCollection与注册到 Windsor 容器的服务组合在一起:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var container = new WindsorContainer();
container.Install(new WindsorConfiguration());
return WindsorRegistrationHelper.CreateServiceProvider(container, services);
}
Run Code Online (Sandbox Code Playgroud)
container.Install(new WindsorConfiguration())允许WindsorConfiguration配置我们的容器。我们可以直接在这种方法中配置容器,但这是保持容器配置井井有条的好方法。我们可以创建许多IWindsorInstaller实现或我们自己的自定义类来配置 Windsor 容器。
WindsorRegistrationHelper.CreateServiceProvider(container, services)创建IServiceProvider使用container和 的services。
我不会在没有先发现的情况下发布所有这些内容。这是一些 NUnit 测试。(我通常会为 DI 配置编写一些基本测试。)
设置创建IServiceProvider类似于应用程序启动时发生的情况。它创建一个容器并应用WindsorConfiguration. 我还直接ServiceCollection向注册了一项服务,以确保两者能够很好地协同工作。然后我将两者组合成一个IServiceProvider.
然后我ICustomerServiceFactory从解析IServiceProvider并验证它ICustomService为每个输入字符串返回正确的实现,包括当字符串不是可识别的依赖项名称时的回退。我也在验证直接注册的服务ServiceCollection是否已解决。
public class Tests
{
private IServiceProvider _serviceProvider;
[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddSingleton<IRegisteredWithServiceCollection, RegisteredWithServiceCollection>();
var container = new WindsorContainer();
container.Install(new WindsorConfiguration());
_serviceProvider = WindsorRegistrationHelper.CreateServiceProvider(container, services);
}
[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);
}
[Test]
public void ServiceProviderReturnsServiceRegisteredWithServiceCollection()
{
var service = _serviceProvider.GetService<IRegisteredWithServiceCollection>();
Assert.IsInstanceOf<RegisteredWithServiceCollection>(service);
}
}
Run Code Online (Sandbox Code Playgroud)
现在我已经弄清楚了,如果我真的需要这种功能,我可能会使用它。如果您尝试将 Windsor 与 .NET Core 结合使用并第一次看到它的抽象工厂实现,那看起来会更糟。这是另一篇文章,其中包含有关 Windsor 抽象工厂的更多信息,但不涉及 .NET Core。
这是一种比将您的应用程序配置为使用另一个 IoC 容器然后配置该容器更容易的方法。在与 Windsor 一起解决这个问题之后,这个解决方案似乎容易多了。
如果您可以使用每个服务实现的单例实例,则此方法是最简单的。
我们将从一个接口、一些实现和我们可以注入的工厂开始,它将根据一些输入返回在运行时选择的实现。
public interface ICustomService { }
public class CustomServiceOne : ICustomService { }
public class CustomServiceTwo : ICustomService { }
public class CustomServiceThree : ICustomService { }
public interface ICustomServiceFactory
{
ICustomService Create(string input);
}
Run Code Online (Sandbox Code Playgroud)
这是工厂的一个非常粗略的实现。(没有使用字符串常量,或者根本没有打磨它。)
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"];
}
}
Run Code Online (Sandbox Code Playgroud)
这假定您已经注册CustomServiceOne,CustomServiceTwo等用的IServiceCollection。它们不会被注册为接口实现,因为这不是我们解决它们的方式。这个类将简单地解析每一个并将它们放入字典中,以便您可以按名称检索它们。
在这种情况下,工厂方法接受一个字符串,但您可以检查任何类型或多个参数以确定要返回的实现。甚至使用字符串作为字典键也是任意的。而且,作为一个例子,我提供了回退行为来返回一些默认实现。如果您无法确定要返回的正确实现,则抛出异常可能更有意义。
根据您的需要,另一种选择是在需要时在工厂内解决实施问题。我尽量让大多数类保持无状态,以便我可以解析和重用单个实例。
要IServiceCollection在启动时注册工厂,我们将执行以下操作:
services.AddSingleton<ICustomServiceFactory>(provider =>
new CustomServiceFactory(provider));
Run Code Online (Sandbox Code Playgroud)
在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);
}
}
Run Code Online (Sandbox Code Playgroud)
与 Windsor 示例中一样,编写此代码是为了避免对组合根外部的容器进行任何引用。如果某个类依赖ICustomServiceFactory并且ICustomService您可以在此实现、Windsor 实现或工厂的任何其他实现之间切换。
| 归档时间: |
|
| 查看次数: |
5302 次 |
| 最近记录: |