У меня есть службы, которые получены из того же интерфейса.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
Как правило, другие контейнеры IoC, такие как Unity
позволяют вам регистрировать конкретные реализации некоторыми, Key
которые их различают.
В ASP.NET Core, как мне зарегистрировать эти сервисы и разрешить их во время выполнения на основе некоторого ключа?
Я не вижу каких-либо Add
методов Service, которые принимают параметр key
или name
, которые обычно используются для различения конкретной реализации.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services of the same interface?
}
public MyController:Controller
{
public void DoSomething(string key)
{
// How do I resolve the service by key?
}
}
Шаблон фабрики - единственный вариант здесь?
Update1
я пошел , хотя в статье здесь , что показывает , как использовать шаблон фабрики , чтобы получить экземпляры служб , когда у нас есть несколько реализаций конкретных. Тем не менее, это все еще не полное решение. Когда я звоню_serviceProvider.GetService()
метод, я не могу вставить данные в конструктор.
Например, рассмотрим это:
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
Как можно _serviceProvider.GetService()
вставить соответствующую строку подключения? В Unity или любой другой библиотеке IoC мы можем сделать это при регистрации типа. Я могу использовать IOption , что потребует от меня ввода всех настроек. Я не могу вставить конкретную строку подключения в службу.
Также обратите внимание, что я стараюсь избегать использования других контейнеров (включая Unity), потому что тогда я должен зарегистрировать все остальное (например, Controllers) в новом контейнере.
Кроме того, использование фабричного шаблона для создания экземпляров сервиса противоречит DIP, так как это увеличивает количество зависимостей, которые клиент имеет здесь. .
Итак, я думаю, что в стандартном DI в ASP.NET Core отсутствуют две вещи:
- Возможность регистрации экземпляров с помощью ключа
- Возможность вставлять статические данные в конструкторы при регистрации
Update1
быть перенесен на другой вопрос, так как внедрение вещей в конструкторы сильно отличается от разработки, какой объект построитьОтветы:
Я сделал простой обходной путь, используя,
Func
когда я оказался в этой ситуации.Сначала объявите общего делегата:
Затем
Startup.cs
настройте несколько конкретных регистраций и сопоставьте их вручную:И используйте его из любого класса, зарегистрированного в DI:
Имейте в виду, что в этом примере ключом для разрешения является строка, для простоты и потому, что OP запрашивал именно этот случай.
Но вы можете использовать любой пользовательский тип разрешения в качестве ключа, так как вы обычно не хотите, чтобы огромный n-регистр переключал ваш код. Зависит от того, как масштабируется ваше приложение.
источник
Другой вариант - использовать метод расширения
GetServices
изMicrosoft.Extensions.DependencyInjection
.Зарегистрируйте свои услуги как:
Затем решите немного Linq:
или
(предполагая, что
IService
имеет строковое свойство с именем «Имя»)Убедитесь, что есть
using Microsoft.Extensions.DependencyInjection;
Обновить
AspNet 2.1 источник:
GetServices
источник
IEnumerable<IService>
Это не поддерживается
Microsoft.Extensions.DependencyInjection
.Но вы можете подключить другой механизм внедрения зависимостей, например,
StructureMap
увидеть его домашнюю страницу и проект GitHub .Это совсем не сложно:
Добавьте зависимость к StructureMap в ваш
project.json
:Вставьте его в конвейер ASP.NET внутри
ConfigureServices
и зарегистрируйте ваши классы (см. Документацию)Затем, чтобы получить именованный экземпляр, вам нужно будет запросить
IContainer
Вот и все.
Для примера, чтобы построить, вам нужно
источник
Вы правы, встроенный контейнер ASP.NET Core не имеет концепции регистрации нескольких сервисов и последующего извлечения конкретного, как вы предполагаете, фабрика - единственное реальное решение в этом случае.
Кроме того, вы можете переключиться на сторонний контейнер, такой как Unity или StructureMap, который предоставляет необходимое решение (см. Здесь: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- заместитель по умолчанию-услуги-контейнер ).
источник
Я столкнулся с той же проблемой и хочу рассказать, как я ее решил и почему.
Как вы упомянули, есть две проблемы. Первый:
Итак, какие у нас есть варианты? Люди предлагают два:
Используйте собственную фабрику (как
_myFactory.GetServiceByKey(key)
)Используйте другой двигатель DI (как
_unityContainer.Resolve<IService>(key)
)Фактически оба варианта являются фабриками, потому что каждый контейнер IoC также является фабрикой (хотя и с высокой степенью конфигурации и сложности). И мне кажется, что другие варианты также являются вариациями паттерна Factory.
Так какой вариант лучше? Здесь я согласен с @Sock, который предложил использовать собственную фабрику, и именно поэтому.
Во-первых, я всегда стараюсь избегать добавления новых зависимостей, когда они действительно не нужны. Поэтому я согласен с вами в этом вопросе. Более того, использование двух структур DI хуже, чем создание собственной фабричной абстракции. Во втором случае вы должны добавить новую зависимость от пакета (например, Unity), но в зависимости от нового интерфейса фабрики здесь меньше зла. Я считаю, что основная идея ASP.NET Core DI - это простота. Он поддерживает минимальный набор функций по принципу KISS . Если вам нужна дополнительная функция, то сделай сам или воспользуйся соответствующим Plungin который реализует желаемую функцию (принцип Open Closed).
Во-вторых, часто нам нужно внедрить много именованных зависимостей для одного сервиса. В случае Unity вам может потребоваться указать имена для параметров конструктора (используя
InjectionConstructor
). Эта регистрация использует отражение и некоторую умную логику, чтобы угадать аргументы для конструктора. Это также может привести к ошибкам во время выполнения, если регистрация не соответствует аргументам конструктора. С другой стороны, при использовании вашей собственной фабрики вы имеете полный контроль над тем, как предоставить параметры конструктора. Это более читабельно и решается во время компиляции. Поцелуй принцип снова.Вторая проблема:
Во-первых, я согласен с вами, что в зависимости от новых вещей, таких как
IOptions
(и, следовательно, от пакетаMicrosoft.Extensions.Options.ConfigurationExtensions
) не очень хорошая идея. Я видел некоторые дискуссии о том,IOptions
где были разные мнения о его пользе. Опять же, я стараюсь избегать добавления новых зависимостей, когда они действительно не нужны. Это действительно нужно? Я думаю нет. В противном случае каждая реализация должна зависеть от нее без какой-либо явной необходимости, исходящей от этой реализации (для меня это выглядит как нарушение ISP, где я тоже с вами согласен). Это также верно в отношении зависимости от завода, но в этом случае это может избежать.Для этой цели ASP.NET Core DI обеспечивает очень хорошую перегрузку:
источник
IServiceA
в вашем случае. Поскольку вы используете методы fromIService
only, у вас должна быть зависимостьIService
только от.services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
для первого класса иservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
для второго.Я просто ввожу IEnumerable
ConfigureServices в Startup.cs
Папка служб
MyController.cs
Extensions.cs
источник
Большинство ответов здесь нарушают принцип единой ответственности (сервисный класс не должен разрешать зависимости самостоятельно) и / или использует анти-паттерн поиска сервисов.
Еще один способ избежать этих проблем:
Я написал статью с более подробной информацией: Внедрение зависимостей в .NET: способ обойти пропущенные именованные регистрации
источник
Немного опоздал на эту вечеринку, но вот мое решение: ...
Startup.cs или Program.cs если универсальный обработчик ...
Интерфейс IMyInterface T Interface
Конкретные реализации IMyInterface T
Надеемся, что если есть какие-то проблемы с этим, кто-то будет любезно указать, почему это неправильный способ сделать это.
источник
IMyInterface<CustomerSavedConsumer>
иIMyInterface<ManagerSavedConsumer>
это разные типы услуг - это не отвечает на вопросы ОП вообще.По-видимому, вы можете просто добавить IEnumerable из вашего интерфейса сервиса! А затем найдите экземпляр, который вы хотите использовать LINQ.
Мой пример для сервиса AWS SNS, но вы действительно можете сделать то же самое для любого внедренного сервиса.
Запускать
SNSConfig
appsettings.json
SNS Factory
Теперь вы можете получить услугу SNS для региона, который вы хотите, в вашей пользовательской службе или контроллере
источник
Фабричный подход, безусловно, жизнеспособен. Другой подход заключается в использовании наследования для создания отдельных интерфейсов, которые наследуются от IService, реализации унаследованных интерфейсов в ваших реализациях IService и регистрации унаследованных интерфейсов, а не базы. Является ли добавление иерархии наследования или фабрик «правильной» моделью, все зависит от того, с кем вы говорите. Мне часто приходится использовать этот шаблон при работе с несколькими поставщиками баз данных в одном приложении, которое использует универсальный, такой как
IRepository<T>
, в качестве основы для доступа к данным.Примеры интерфейсов и реализаций:
Контейнер:
источник
Necromancing.
Я думаю, что люди здесь заново изобретают колесо - и плохо, если можно так сказать ...
Если вы хотите зарегистрировать компонент по ключу, просто используйте словарь:
А затем зарегистрируйте словарь в сервис-коллекции:
если вы не хотите получать словарь и обращаться к нему по ключу, вы можете скрыть словарь, добавив дополнительный метод поиска ключей в коллекцию сервисов:
(использование делегата / закрытия должно дать потенциальному сопровождающему шанс на понимание происходящего - обозначение стрелки немного загадочно)
Теперь вы можете получить доступ к вашим типам с
или
Как мы видим, первый из них просто лишний, потому что вы можете сделать это точно со словарем, не требуя замыканий и AddTransient (и если вы используете VB, даже скобки не будут другими):
(чем проще, тем лучше - вы можете использовать его как метод расширения)
Конечно, если вам не нравится словарь, вы также можете снабдить свой интерфейс свойством
Name
(или чем-то еще) и посмотреть его по ключу:Но для этого необходимо изменить интерфейс, чтобы приспособить свойство, и циклический просмотр множества элементов должен быть намного медленнее, чем поиск по ассоциативному массиву (словарь).
Приятно осознавать, что это можно сделать без диктиона.
Это всего лишь мои $ 0,05
источник
IDispose
реализована, кто несет ответственность за ее использование? Вы зарегистрировали словарь какSingleton
с момента моего поста выше, я перешел на общий заводской класс
использование
Реализация
расширение
источник
Хотя, кажется, @Miguel A. Arilla четко указал на это, и я проголосовал за него, я создал поверх его полезного решения еще одно решение, которое выглядит аккуратно, но требует гораздо больше работы.
Это определенно зависит от вышеуказанного решения. В общем, я создал нечто похожее
Func<string, IService>>
и назвал этоIServiceAccessor
интерфейсом, а затем мне пришлось добавить еще несколько расширенийIServiceCollection
как таковых:Accessor службы выглядит так:
В результате вы сможете зарегистрировать сервисы с именами или именованными экземплярами, как мы это делали с другими контейнерами. Например:
Пока этого достаточно, но чтобы завершить работу, лучше добавить больше методов расширения, чтобы охватить все типы регистраций, следуя тому же подходу.
Был еще один пост о stackoverflow, но я не могу найти его, где автор подробно объяснил, почему эта функция не поддерживается и как ее обойти, в основном аналогично тому, что сказал @Miguel. Это был хороший пост, хотя я не согласен с каждым пунктом, потому что я думаю, что есть ситуации, когда вам действительно нужны именованные экземпляры. Я опубликую эту ссылку здесь, как только найду ее снова.
На самом деле вам не нужно передавать этот селектор или аксессор:
Я использую следующий код в своем проекте, и до сих пор он работал хорошо.
источник
Я создал библиотеку для этого, которая реализует некоторые приятные функции. Код можно найти на GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
Использование просто:
В приведенном выше примере обратите внимание, что для каждой именованной регистрации вы также указываете время жизни или Singleton, Scoped или Transient.
Вы можете разрешить услуги одним из двух способов, в зависимости от того, насколько вам удобны зависимости от этих пакетов:
или
Я специально разработал эту библиотеку для работы с Microsoft.Extensions.DependencyInjection - например:
Когда вы регистрируете именованные сервисы, любые типы, которые вы регистрируете, могут иметь конструкторы с параметрами - они будут удовлетворяться через DI, так же, как
AddTransient<>
,AddScoped<>
иAddSingleton<>
методы работы обычно.Для временных и указанных именованных служб реестр создает
ObjectFactory
так, что он может очень быстро активировать новые экземпляры типа при необходимости. Это намного быстрее, чем другие подходы, и соответствует тому, как Microsoft.Extensions.DependencyInjection работает.источник
Мое решение для того, что оно стоит ... подумал о переходе на замок Виндзор, так как не могу сказать, что мне понравилось любое из решений выше. Сожалею!!
Создайте свои различные реализации
Постановка на учет
Использование конструктора и экземпляра ...
источник
Расширение решения @rnrneverdies. Вместо ToString () также могут использоваться следующие параметры: 1) с реализацией общего свойства, 2) сервис сервисов, предложенный @Craig Brunetti.
источник
Прочитав ответы здесь и статьи в других местах, я смог заставить его работать без всяких условий. Если у вас есть несколько реализаций одного и того же интерфейса, DI добавит их в коллекцию, поэтому можно будет получить нужную версию из коллекции, используя
typeof
.источник
var serviceA = new ServiceA();
Хотя готовая реализация этого не предлагает, вот пример проекта, который позволяет вам регистрировать именованные экземпляры, а затем вставлять INamedServiceFactory в ваш код и извлекать экземпляры по имени. В отличие от других решений здесь, это позволит вам зарегистрировать несколько экземпляров одной и той же реализации. но настроенных по-разному.
https://github.com/macsux/DotNetDINamedInstances
источник
Как насчет услуги для услуг?
Если бы у нас был интерфейс INamedService (со свойством .Name), мы могли бы написать расширение IServiceCollection для .GetService (имя строки), где расширение будет принимать этот строковый параметр, и делать .GetServices () для себя и в каждом возвращаемом instance, найдите экземпляр, чье имя INamedService.Name соответствует заданному имени.
Как это:
Следовательно, ваш IMyService должен реализовывать INamedService, но вы получите разрешение на основе ключей, которое вы хотите, верно?
Чтобы быть справедливым, необходимость иметь даже этот интерфейс INamedService кажется уродливой, но если вы хотите пойти дальше и сделать вещи более элегантными, то [NamedServiceAttribute ("A")] в реализации / классе можно найти с помощью кода в этом расширение, и это будет работать так же хорошо. Чтобы быть еще более справедливым, рефлексия медленная, поэтому может потребоваться оптимизация, но, честно говоря, это то, с чем должен был помочь механизм DI. Скорость и простота - все они внесли большой вклад в TCO.
В общем, нет необходимости в явной фабрике, потому что «поиск именованного сервиса» является такой концепцией многократного использования, а фабричные классы не масштабируются как решение. И Func <> кажется нормальным, но блок переключателей настолько прост , и, опять же, вы будете писать Funcs так же часто, как и фабрики. Начните с простого, многоразового использования, с меньшим количеством кода, и если это не поможет вам, то пойдите сложным.
источник
Я столкнулся с той же проблемой, и я работал с простым расширением, чтобы разрешить именованные службы. Вы можете найти это здесь:
Это позволяет вам добавлять столько (именованных) сервисов, сколько вы хотите, вот так:
Библиотека также позволяет легко реализовать «фабричный шаблон», например так:
Надеюсь, поможет
источник
Я создал собственное расширение поверх
IServiceCollection
используемогоWithName
:ServiceCollectionTypeMapper
одноэлементно экземпляр, картыIService
>NameOfService
>Implementation
где интерфейс может иметь множество реализаций с разными именами, это позволяет регистрировать типы , чем мы можем решить , когда дите потребность и другой подход , чем решительность несколько услуг , чтобы выбрать то , что мы хотим.Чтобы зарегистрировать новый сервис:
Чтобы разрешить обслуживание, нам нужно расширение,
IServiceProvider
как это.Когда решите:
Помните, что serviceProvider может быть вставлен в конструктор в нашем приложении как
IServiceProvider
.Надеюсь, это поможет.
источник