Это потребовало создания нескольких дополнительных классов и модульного теста.Тест разрешает службы из контейнера и подтверждает, что они были разрешены и введены в соответствии с техническими требованиями вашего вопроса.Если мы сможем настроить контейнер так, чтобы тест проходил успешно, мы добьемся успеха.
public interface IServiceA
{
ISharedService SharedService { get; }
}
public interface IServiceB
{
ISharedService SharedService { get; }
}
public class ServiceA : IServiceA
{
public ServiceA(ISharedService sharedService)
{
SharedService = sharedService;
}
public ISharedService SharedService { get; }
}
public class ServiceB : IServiceB
{
public ServiceB(ISharedService sharedService)
{
SharedService = sharedService;
}
public ISharedService SharedService { get; }
}
public interface ISharedService { }
public class SharedService : ISharedService { }
Идея состоит в том, что ServiceA
и ServiceB
оба зависят от ISharedService
.Нам нужно разрешить каждый из них несколько раз для проверки:
- Когда мы разрешаем
IServiceA
, всегда ли мы получаем один и тот же экземпляр SharedService
? - Когда мы решаем
IServiceB
, всегда ли мы получаем один и тот же экземпляр SharedService
? - Когда мы разрешаем
IServiceA
и IServiceB
, получаем ли мы разные экземпляры SharedService
?
Вот схема модульного теста:
public class DiscreteSingletonTests
{
[TestMethod]
public void ResolvesDiscreteSingletons()
{
var serviceProvider = GetServiceProvider();
var resolvedA1 = serviceProvider.GetService<IServiceA>();
var resolvedA2 = serviceProvider.GetService<IServiceA>();
var resolvedB1 = serviceProvider.GetService<IServiceB>();
var resolvedB2 = serviceProvider.GetService<IServiceB>();
// Make sure we're resolving multiple instances of each.
// That way we'll know that the "discrete" singleton is really working.
Assert.AreNotSame(resolvedA1, resolvedA2);
Assert.AreNotSame(resolvedB1, resolvedB2);
// Make sure that all instances of ServiceA or ServiceB receive
// the same instance of SharedService.
Assert.AreSame(resolvedA1.SharedService, resolvedA2.SharedService);
Assert.AreSame(resolvedB1.SharedService, resolvedB2.SharedService);
// ServiceA and ServiceB are not getting the same instance of SharedService.
Assert.AreNotSame(resolvedA1.SharedService, resolvedB1.SharedService);
}
private IServiceProvider GetServiceProvider()
{
// This is the important part.
// What goes here?
// How can we register our services in such a way
// that the unit test will pass?
}
}
Мы не сможем сделать это только с помощью IServiceCollection/IServiceProvider
, если мы не сделаем некоторые действительно уродливые вещи, которые я просто не хочу делать.Вместо этого мы можем использовать различные контейнеры IoC, как рекомендовано этой документацией :
Встроенный служебный контейнер предназначен для удовлетворения потребностей платформы и большинства пользовательских приложений.Мы рекомендуем использовать встроенный контейнер, если вам не нужна особая функция, которую он не поддерживает.
Другими словами, если нам нужны все навороты, мы должны использовать другой контейнер.Вот несколько примеров того, как это сделать:
Autofac
В этом решении используется Autofac.Extensions.DependencyInjection .Вы можете изменить его в соответствии с примером, в котором используется класс Startup
.
private IServiceProvider GetServiceProvider()
{
var serviceCollection = new ServiceCollection();
var builder = new ContainerBuilder();
builder.Populate(serviceCollection);
builder.RegisterType<SharedService>().As<ISharedService>()
.Named<ISharedService>("ForServiceA")
.SingleInstance();
builder.RegisterType<SharedService>().As<ISharedService>()
.Named<ISharedService>("ForServiceB")
.SingleInstance();
builder.Register(ctx => new ServiceA(ctx.ResolveNamed<ISharedService>("ForServiceA")))
.As<IServiceA>();
builder.Register(ctx => new ServiceB(ctx.ResolveNamed<ISharedService>("ForServiceB")))
.As<IServiceB>();
var container = builder.Build();
return new AutofacServiceProvider(container);
}
Мы регистрируем ISharedService
дважды под разными именами, каждое в виде синглтона.Затем при регистрации IServiceA
и ServiceB
мы указываем имя зарегистрированного компонента для использования.
IServiceA
и IServiceB
являются временными (не указано, но это значение по умолчанию).
Castle Windsor
В этом решении используется Castle.Windsor.MsDependencyInjection :
private IServiceProvider GetServiceProvider()
{
var container = new WindsorContainer();
var serviceCollection = new ServiceCollection();
container.Register(
Component.For<ISharedService, SharedService>().Named("ForServiceA"),
Component.For<ISharedService, SharedService>().Named("ForServiceB"),
Component.For<IServiceA, ServiceA>()
.DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceA"))
.LifestyleTransient(),
Component.For<IServiceB, ServiceB>()
.DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceB"))
.LifestyleTransient()
);
return WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection);
}
Мы регистрируемсяISharedService
дважды с разными именами, каждое как одиночка.(Не указано, но это по умолчанию.) Затем, при регистрации IServiceA
и ServiceB
мы указываем имя зарегистрированного компонента для использования.
В обоих случаях ясоздать ServiceCollection
и ничего с ним не делать.Дело в том, что вы все равно можете регистрировать типы напрямую с помощью IServiceCollection
, а не через Autofac или Windsor.Так что если вы зарегистрировали это:
serviceCollection.AddTransient<Whatever>();
... вы можете решить Whatever
.Добавление другого контейнера не означает, что вам теперь нужно все регистрировать в этом контейнере.