#c# #asp.net-core #dependency-injection
#c# #asp.net-ядро #внедрение зависимостей #asp.net-core
Вопрос:
Я столкнулся с небольшой проблемой, пытаясь использовать внедрение зависимостей, и мне нужна помощь.
У меня есть сервис IService
, который реализован несколькими различными способами в моем приложении.
ServiceA : IService { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IService { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IService serviceA, IService serviceB) {...} }
В моем startup.cs
файле я выберу, какой из них использовать, на основе параметра в моем файле конфигурации. Что-то вроде этого:
var service = Configuration["AppConfig:Service:ID"];
switch(service) {
case "A":
services.AddTransient<IService, ServiceA>();
case "B":
services.AddTransient<IService, ServiceB>();
case "C":
// ??
}
Я могу очень легко создавать и использовать службы A и B. Я заранее ввожу их зависимости A и B, и они создаются просто отлично. Проблема связана с третьей службой. Мне нужно внедрить в него две другие службы. Мой вопрос: как лучше всего это сделать?
Есть ли способ, которым я могу создать конкретные реализации сервисов A и B, но каким-то образом использовать введенные зависимости A и B в их конструкторе? Должен ли я возиться с интерфейсами, чтобы заставить это работать? Может быть, мне нужно изменить конструктор ServiceC, чтобы использовать конкретные реализации A amp; B?
Обновлено: не знаю, лучшее ли это решение, но в итоге я сделал следующее, чтобы заставить его работать.
...
case "C":
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<IService>(s =>
new ServiceC(
s.GetService<ServiceA>(),
s.GetService<ServiceB>()
));
Комментарии:
1. Добавьте более подробную информацию о сервисе C. Если ему действительно нужны эти 2 конкретные реализации, то вам следует изменить конструктор. В противном случае это нарушит принцип подстановки Liskov. Исправьте переключатель, перерывов нет, и ServiceA регистрируется 2 раза.
2. Цель службы C — использовать реализацию службы A, но если служба A не смогла предоставить правильный результат, вернитесь к результату службы B.
3. Создайте фабричный шаблон для требуемых служб и создавайте их экземпляры при необходимости. Не все должно обрабатываться DI()
4. Основываясь на вашем последнем комментарии здесь об использовании сервисов на основе их результата, вы могли бы добиться этого более чистым способом, используя концепцию, аналогичную шаблону стратегии . Конечно, было бы лучше, если бы вы могли предсказать, будет ли результат успешным.
5. @mymemesarespiciest даже если вы не используете Polly напрямую, вы должны увидеть, как это работает, как это решает те же проблемы, которые могут возникнуть у вас
Ответ №1:
Другая альтернатива. Представьте другой интерфейс, чтобы вы могли регистрировать оба ServiceC
и его зависимости одновременно, без какой-либо двусмысленности или циклических ссылок.
interface IService {}
interface IServiceImpl : IService {}
ServiceA : IServiceImpl { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IServiceImpl { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IEnumerable<IServiceImpl> services) {...} }
services.AddTransient<IServiceImpl, ServiceA>();
services.AddTransient<IServiceImpl, ServiceB>();
services.AddTransient<IService, ServiceC>();
Комментарии:
1. Да, это будет. DI обычно вводит первый зарегистрированный тип. Обратите внимание, что я изменил конструктор ServiceC на IEnumerable<T>, чтобы он получал все реализации сервиса.
2. Я понимаю, почему ServiceC в порядке. Мне нужно самостоятельно протестировать настройку для ServiceA и ServiceB в течение этих выходных.
3. Кстати: при тестировании этого, как я должен разрешить ServiceA и ServiceB, например, в контроллере? Путем введения
IServiceImpl
?4. Идея состояла в том, чтобы сохранить
IService
как «общедоступный API» и ссылаться только наIServiceImpl
«внутренне» как на деталь реализации…5. Я вижу, что OP решил это с помощью сервисов. AddTransient<ServiceA>(); services. AddTransient<ServiceB>(); , возможно, пока нет смысла углубляться в это. В любом случае, теперь я понимаю логику вашего ответа, 1 за это 🙂 Спасибо за ваше время, Джереми. BR
Ответ №2:
Должно быть лучшее объяснение наличия таких зависимостей.
Майкрософт.Расширения.DependencyInjection не предоставляет именованные регистрации, как другие контейнеры IoC, т.Е. Castle.Виндзор.
Есть способ, которым вы все еще можете зарегистрировать ServiceC с экземплярами A и B
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<IService>(serviceProvider => {
return new ServiceC((IService)serviceProvider.GetRequiredService(typeof(ServiceA)), (IService)serviceProvider.GetRequiredService(typeof(ServiceB)));
});
Комментарии:
1. Забавно, что вы публикуете это, потому что это почти идентично решению, которое я только что нашел, которое в итоге сработало. Единственное отличие заключается в том, что вам не нужно приводить к IService, потому что эти службы уже реализуют этот интерфейс.
2. ага, здорово! Кстати. Я не обновляюсь во время записи. и достаточно «забавно», что вы так думаете 🙂 Единственная причина для приведения в том, что
GetRequiredService
требуется приведение типов