Внедрение зависимостей .NET Core для многоуровневых множественных зависимостей реализации
Я столкнулся с несколькими сообщениями, в которых один интерфейс реализуется несколькими классами, а зависимость регистрируется и разрешается. Один из таких постов — этот.
Но как разрешить несколько многоуровневых зависимостей?
Например:
public enum ShortEnum { Short, Long }
public interface ISomeValidator
{
bool ValidateInputString(string str);
}
public class ConValidator : ISomeValidator
{
public bool ValidateInputString(string str) => true;
}
public class DonValidator : ISomeValidator
{
public bool ValidateInputString(string str) => false;
}
public class ConProvider : ISomeProvider
{
ISomeValidator conValidator; // Expects instance of ConValidator
public ConProvider(ISomeValidator someValidator)
{
conValidator = someValidator;
}
}
public class DonProvider : ISomeProvider
{
ISomeValidator donValidator; // Expects instance of DonValidator
public DonProvider(ISomeValidator someValidator)
{
donValidator = someValidator;
}
}
можно использовать как ключ. Это означает, что в зависимости от его значения либо
ConProvider
или же
DonProvider
возвращается. Теперь провайдеры зависят от
ISomeValidator
, который, опять же, зависит от значения ключа
ShortEnum
может быть разрешен как экземпляр
ConValidator
или же
DonValidator
.
Другими словами, я хочу построить следующие два графа объектов:
var provider1 = new ConProvider(new ConValidator());
var provider2 = new DonProvider(new DonValidator());
Как лучше всего использовать встроенный в .NET Core 3.1 механизм внедрения зависимостей?
2 ответа
Есть три способа добиться желаемого.
Первый вариант — вернуться к ручному подключению графов объектов путем регистрации делегата, который полностью составляет требуемые графы объектов, например:
services.AddTransient<ISomeProvider>(
c => new ConProvider(new ConValidator());
services.AddTransient<ISomeProvider>(
c => new DonProvider(new DonValidator());
Это, однако, станет довольно громоздким, как только эти классы получат другие зависимости, потому что вы очень быстро начнете полностью обходить DI-контейнер.
Поэтому вместо этого, в качестве второго варианта, вы можете настроить контейнер таким образом, чтобы вам нужно было только
new
два провайдера:
// Note how these validators are -not- registered by their interface!
services.AddTransient<ConValidator>(); // Might have dependencies
services.AddTransient<DonValidator>(); // of its own
services.AddTransient<ISomeProvider>(c =>
new ConProvider(
someValidator: c.GetRequiredService<ConValidator>(),
otherDependency1: c:GetRequiredService<IDependency1>());
services.AddTransient<ISomeProvider>(c =>
new DonProvider(
someValidator: c.GetRequiredService<DonValidator>(),
otherDependency2: c:GetRequiredService<IDependency2>());
Это более гибкое решение по сравнению с первым. Но тем не менее, изменения в конструкторах и заставляют вас обновлять их регистрации.
Для борьбы с этим в качестве третьего варианта можно использовать
ActivatorUtilities
учебный класс. Это поможет вам в достижении Auto-Wiring, когда контейнер определяет, какие зависимости требуются. Это может выглядеть так:
services.AddTransient<ConValidator>();
services.AddTransient<DonValidator>();
services.AddTransient<ISomeProvider>(c =>
ActivatorUtilities.CreateInstance<ConProvider>(
c,
c.GetRequiredService<ConValidator>());
services.AddTransient<ISomeProvider>(c =>
ActivatorUtilities.CreateInstance<DonProvider>(
c,
c.GetRequiredService<DonValidator>());
ActivatorUtilities.CreateInstance
создаст для вас запрошенный класс; в этом случае либо
ConProvider
или же
DonProvider
. Он делает это, просматривая конструктор типа и разрешая все параметры конструктора из предоставленного
c
поставщик услуг. Он разрешает все зависимости от поставщика услуг, за исключением зависимостей, которые вы указали вручную для
CreateInstance
метод. В приведенном выше примере он будет соответствовать
ISomeValidator
зависимость от поставляемого
DonValidator
или же
ConValidator
и вводить их вместо этого.
Если я правильно понял, вы хотите
ConValidator
экземпляр для
ConProvider
а также
DonValidator
экземпляр для
DonProvider
и провайдеры должны быть разрешены на
ShortEnum
ценность.
мы можем изменить зависимости, как показано ниже
public interface ISomeProvider
{
void Method1();
}
public interface ISomeValidator
{
bool ValidateInputString(string str);
}
public class ConValidator : ISomeValidator
{
public bool ValidateInputString(string str)
{
return true;
}
}
public class DonValidator : ISomeValidator
{
public bool ValidateInputString(string str)
{
return false;
}
}
public class ConProvider : ISomeProvider
{
ISomeValidator conValidator; // Expects instance of
//ConValidator
public ConProvider(ValidatorResolver validatorResolver)
{
conValidator = validatorResolver(ShortEnum.Short);
}
public void Method1() {
System.Console.WriteLine("Method1 COnProvider");
}
}
public class DonProvider : ISomeProvider
{
ISomeValidator donValidator; // Expects instance of
//DonValidator
public DonProvider(ValidatorResolver validatorResolver)
{
donValidator = validatorResolver(ShortEnum.Long);
}
public void Method1() {
System.Console.WriteLine("Method1 DonProvider");
}
}
public enum ShortEnum
{
Short,
Long
}
объявить делгейты
public delegate ISomeProvider ProviderResolver(ShortEnum shortEnum);
public delegate ISomeValidator ValidatorResolver(ShortEnum shortEnum);
Потребительское обслуживание
public class SomeOtherService
{
private readonly ISomeProvider _provider;
public SomeOtherService(ProviderResolver providerResolver)
{
_provider = providerResolver(ShortEnum.Short);
//OR
_provider = providerResolver(ShortEnum.Long);
_provider.Method1();
}
}
В
ConfigureServices
метод
StartUp
учебный класс
services.AddTransient<ConValidator>();
services.AddTransient<DonValidator>();
services.AddTransient<ConProvider>();
services.AddTransient<DonProvider>();
services.AddTransient<ProviderResolver>(serviceProvider => (shortEnum) =>
{
switch (shortEnum)
{
case ShortEnum.Short:
return serviceProvider.GetService<ConProvider>();
case ShortEnum.Long:
return serviceProvider.GetService<DonProvider>();
default:
throw new KeyNotFoundException();
}
});
services.AddTransient<ValidatorResolver>(serviceProvider => (shortEnum) =>
{
switch(shortEnum)
{
case ShortEnum.Short:
return serviceProvider.GetService<ConValidator>();
case ShortEnum.Long:
return serviceProvider.GetService<DonValidator>();
default:
throw new KeyNotFoundException();
}
});
services.AddTransient<SomeOtherService>();