Mam usługi pochodzące z tego samego interfejsu.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
Zazwyczaj inne kontenery IoC, takie jak, Unity
pozwalają rejestrować konkretne implementacje przez niektóre, Key
które je wyróżniają.
W programie ASP.NET Core, w jaki sposób mogę zarejestrować te usługi i rozwiązać je w czasie wykonywania na podstawie jakiegoś klucza?
Nie widzę żadnych Add
metod usługi, które pobierają parametr key
lub name
, które zwykle byłyby używane do rozróżnienia konkretnej implementacji.
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?
}
}
Czy wzór fabryczny jest tutaj jedyną opcją?
Aktualizacja
1 Poszedłem przez ten artykuł , który pokazuje, jak używać wzorca fabrycznego, aby uzyskać wystąpienia usług, gdy mamy wiele konkretnych implementacji. Jednak wciąż nie jest to kompletne rozwiązanie. Podczas wywoływania _serviceProvider.GetService()
metody nie mogę wstrzykiwać danych do konstruktora.
Rozważ na przykład:
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;
}
}
Jak _serviceProvider.GetService()
wstrzyknąć odpowiedni ciąg połączenia? W Unity lub innej bibliotece IoC możemy to zrobić przy rejestracji typu. Mogę jednak użyć IOption , co wymaga ode mnie wprowadzenia wszystkich ustawień. Nie mogę wstrzyknąć określonego ciągu połączenia do usługi.
Zauważ też, że staram się unikać używania innych kontenerów (w tym Unity), ponieważ wtedy muszę zarejestrować wszystko inne (np. Kontrolery) w nowym kontenerze.
Ponadto używanie wzorca fabrycznego do tworzenia instancji usług jest niezgodne z DIP, ponieważ zwiększa liczbę zależności, które klient ma tutaj .
Tak więc myślę, że domyślnemu DI w ASP.NET Core brakuje dwóch rzeczy:
- Możliwość rejestrowania instancji za pomocą klucza
- Możliwość wstrzykiwania statycznych danych do konstruktorów podczas rejestracji
Update1
przejść do innego pytania, ponieważ wstrzykiwanie rzeczy do konstruktorów różni się bardzo od określenia, który obiekt ma zostać zbudowanyOdpowiedzi:
Func
Kiedy znalazłem się w takiej sytuacji, zrobiłem proste obejście .Najpierw zadeklaruj udostępnionego delegata:
Następnie
Startup.cs
skonfiguruj wiele konkretnych rejestracji i ręczne mapowanie tych typów:I użyj go z dowolnej klasy zarejestrowanej w DI:
Należy pamiętać, że w tym przykładzie kluczem do rozwiązania jest ciąg znaków, ze względu na prostotę i ponieważ OP poprosił w szczególności o tę sprawę.
Ale możesz użyć dowolnego niestandardowego typu rozdzielczości jako klucza, ponieważ zwykle nie chcesz, aby ogromny przełącznik n-case gnił kod. Zależy od sposobu skalowania aplikacji.
źródło
Inną opcją jest użycie metody rozszerzenia
GetServices
zMicrosoft.Extensions.DependencyInjection
.Zarejestruj swoje usługi jako:
Następnie rozwiąż z odrobiną Linq:
lub
(zakładając, że
IService
ma właściwość ciągu o nazwie „Nazwa”)Upewnij się, że masz
using Microsoft.Extensions.DependencyInjection;
Aktualizacja
Źródło AspNet 2.1:
GetServices
źródło
IEnumerable<IService>
To nie jest obsługiwane przez
Microsoft.Extensions.DependencyInjection
.Możesz jednak podłączyć inny mechanizm wstrzykiwania zależności, na przykład
StructureMap
Zobacz stronę główną i projekt GitHub .To wcale nie jest trudne:
Dodaj zależność do StructureMap w swoim
project.json
:Wstaw go do potoku ASP.NET wewnątrz
ConfigureServices
i zarejestruj swoje klasy (zobacz dokumenty)Następnie, aby uzyskać nazwane wystąpienie, musisz poprosić o
IContainer
Otóż to.
Aby zbudować przykład, potrzebujesz
źródło
Masz rację, wbudowany kontener ASP.NET Core nie ma koncepcji rejestrowania wielu usług, a następnie pobierania konkretnej, jak sugerujesz, fabryka jest jedynym realnym rozwiązaniem w tym przypadku.
Alternatywnie możesz przełączyć się na pojemnik innej firmy, taki jak Unity lub StructureMap, który zapewnia potrzebne rozwiązanie (udokumentowane tutaj: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- domyślny kontener usług ).
źródło
Napotkałem ten sam problem i chcę podzielić się tym, jak go rozwiązałem i dlaczego.
Jak wspomniałeś, istnieją dwa problemy. Pierwszy:
Jakie mamy opcje? Ludzie sugerują dwa:
Użyj niestandardowej fabryki (jak
_myFactory.GetServiceByKey(key)
)Użyj innego silnika DI (jak
_unityContainer.Resolve<IService>(key)
)W rzeczywistości obie opcje są fabrykami, ponieważ każdy kontener IoC jest również fabryką (wysoce konfigurowalny i skomplikowany). I wydaje mi się, że inne opcje są również odmianami wzoru fabrycznego.
Więc która opcja jest lepsza? Tutaj zgadzam się z @Sock, który zaproponował użycie fabryki niestandardowej i dlatego.
Po pierwsze, zawsze staram się unikać dodawania nowych zależności, gdy nie są one naprawdę potrzebne. Zgadzam się z tobą w tym punkcie. Co więcej, użycie dwóch struktur DI jest gorsze niż tworzenie niestandardowej abstrakcji fabryki. W drugim przypadku musisz dodać nową zależność pakietu (jak Unity), ale w zależności od nowego interfejsu fabrycznego jest tutaj mniej zła. Myślę, że główną ideą ASP.NET Core DI jest prostota. Zachowuje minimalny zestaw funkcji zgodnie z zasadą KISS . Jeśli potrzebujesz dodatkowej funkcji, zrób to sam lub skorzystaj z odpowiedniego Plungin, który implementuje pożądaną funkcję (zasada otwartej zamkniętej).
Po drugie, często musimy wprowadzić wiele nazwanych zależności dla pojedynczej usługi. W przypadku Unity może być konieczne podanie nazw parametrów konstruktora (za pomocą
InjectionConstructor
). Ta rejestracja używa refleksji i inteligentnej logiki do odgadywania argumentów dla konstruktora. Może to również prowadzić do błędów w czasie wykonywania, jeśli rejestracja nie pasuje do argumentów konstruktora. Z drugiej strony, korzystając z własnej fabryki, masz pełną kontrolę nad tym, jak podać parametry konstruktora. Jest bardziej czytelny i rozwiązany w czasie kompilacji. Znowu zasada KISS .Drugi problem:
Po pierwsze, zgadzam się z tobą, że w zależności od nowych rzeczy, takich jak
IOptions
(a zatem od pakietuMicrosoft.Extensions.Options.ConfigurationExtensions
), nie jest dobrym pomysłem. Widziałem kilka dyskusji na tematIOptions
różnych opinii na temat jego zalet. Ponownie staram się unikać dodawania nowych zależności, gdy nie są one naprawdę potrzebne. Czy to naprawdę potrzebne? Myśle że nie. W przeciwnym razie każda implementacja musiałaby na niej polegać bez wyraźnej potrzeby wynikającej z tej implementacji (dla mnie wygląda to na naruszenie usługodawcy internetowego, w przypadku którego również się z tobą zgadzam). Dotyczy to również zależności od fabryki, ale w tym przypadku można tego uniknąć.ASP.NET Core DI zapewnia bardzo ładne przeciążenie w tym celu:
źródło
IServiceA
. Ponieważ używaszIService
tylko metod od , powinieneś mieć zależnośćIService
tylko od.services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
dla pierwszej klasy iservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
dla drugiej.Po prostu wstrzykuję IEnumerable
ConfigureServices w Startup.cs
Folder usług
MyController.cs
Extensions.cs
źródło
Większość odpowiedzi tutaj narusza zasadę pojedynczej odpowiedzialności (klasa usług nie powinna sama rozwiązywać zależności) i / lub używa anty-wzorca lokalizatora usług.
Inną opcją uniknięcia tych problemów jest:
Napisałem artykuł z bardziej szczegółowymi informacjami: Wstrzykiwanie zależności w .NET: sposób obejścia brakujących nazwanych rejestracji
źródło
Trochę za późno na tę imprezę, ale oto moje rozwiązanie: ...
Startup.cs lub Program.cs, jeśli Generic Handler ...
IMyInterface T Interface Setup
Konkretne wdrożenia interfejsu IMyInterface of T
Mam nadzieję, że jeśli wystąpią jakiekolwiek problemy z robieniem tego w ten sposób, ktoś uprzejmie wskaże, dlaczego jest to niewłaściwy sposób.
źródło
IMyInterface<CustomerSavedConsumer>
iIMyInterface<ManagerSavedConsumer>
są różnymi rodzajami usług - to wcale nie odpowiada na pytanie PO.Najwyraźniej możesz po prostu wstrzyknąć IEnumerable interfejsu usługi! A następnie znajdź instancję, której chcesz używać LINQ.
Mój przykład dotyczy usługi AWS SNS, ale możesz zrobić to samo dla każdej wstrzykniętej usługi.
Uruchomienie
SNSConfig
appsettings.json
SNS Factory
Teraz możesz uzyskać usługę SNS dla regionu, który chcesz w niestandardowej usłudze lub kontrolerze
źródło
Podejście fabryczne jest z pewnością realne. Innym podejściem jest wykorzystanie dziedziczenia do tworzenia indywidualnych interfejsów, które dziedziczą z IService, implementacji odziedziczonych interfejsów w implementacjach IService i rejestrowania odziedziczonych interfejsów zamiast bazy. To, czy dodanie hierarchii dziedziczenia lub fabryk jest „właściwym” wzorcem, wszystko zależy od tego, z kim rozmawiasz. Często muszę używać tego wzorca w kontaktach z wieloma dostawcami baz danych w tej samej aplikacji, która używa ogólnych, takich jak
IRepository<T>
podstawa dostępu do danych.Przykładowe interfejsy i implementacje:
Pojemnik:
źródło
Nekromancja.
Myślę, że ludzie tutaj wymyślają na nowo koło - i źle, jeśli mogę tak powiedzieć ...
Jeśli chcesz zarejestrować komponent według klucza, po prostu użyj słownika:
A następnie zarejestruj słownik w kolekcji usług:
jeśli następnie nie chcesz uzyskać słownika i uzyskiwać do niego dostęp kluczem, możesz ukryć słownik, dodając dodatkową metodę wyszukiwania klucza do zbioru usług:
(użycie delegata / zamknięcia powinno dać potencjalnemu opiekunowi szansę na zrozumienie, co się dzieje - notacja ze strzałkami jest nieco tajemnicza)
Teraz możesz uzyskać dostęp do swoich typów za pomocą jednego z nich
lub
Jak widzimy, pierwszy jest po prostu całkowicie zbędny, ponieważ możesz zrobić to dokładnie za pomocą słownika, bez konieczności zamykania i AddTransient (a jeśli używasz VB, nawet nawiasy klamrowe będą się różnić):
(prostsze jest lepsze - możesz jednak użyć go jako metody rozszerzenia)
Oczywiście, jeśli nie podoba ci się słownik, możesz również wyposażyć interfejs w właściwość
Name
(lub cokolwiek innego) i sprawdzić to według klucza:Ale to wymaga zmiany interfejsu w celu dostosowania go do właściwości, a przechodzenie przez wiele elementów powinno być znacznie wolniejsze niż wyszukiwanie tablic asocjacyjnych (słownik).
Miło jest wiedzieć, że można tego dokonać bez słownika.
To tylko moje 0,05 $
źródło
IDispose
wdrożona, kto jest odpowiedzialny za zbywanie usługi? Zarejestrowałeś słownik jakoSingleton
od czasu mojego postu przeniosłem się do Generic Factory Class
Stosowanie
Realizacja
Rozbudowa
źródło
Chociaż wydaje się, że @Mueluel A. Arilla wyraźnie to podkreślił i głosowałem za nim, stworzyłem na jego użyteczne rozwiązanie inne rozwiązanie, które wygląda schludnie, ale wymaga dużo więcej pracy.
To zdecydowanie zależy od powyższego rozwiązania. Zasadniczo stworzyłem coś podobnego
Func<string, IService>>
i nazwałem toIServiceAccessor
interfejsem, a następnie musiałem dodać kilka takich rozszerzeńIServiceCollection
:Usługa Accessor wygląda następująco:
W rezultacie będziesz mógł zarejestrować usługi o nazwach lub nazwanych instancjach, tak jak to robiliśmy z innymi kontenerami .. na przykład:
Na razie wystarczy, ale aby zakończyć pracę, lepiej jest dodać więcej metod rozszerzenia, aby móc objąć wszystkie typy rejestracji według tego samego podejścia.
Był inny post na temat przepełnienia stosu, ale nie mogę go znaleźć, w którym plakat szczegółowo wyjaśnił, dlaczego ta funkcja nie jest obsługiwana i jak ją obejść, w zasadzie podobny do tego, co stwierdził @Miguel. To był miły post, chociaż nie zgadzam się z każdym punktem, ponieważ myślę, że są sytuacje, w których naprawdę potrzebujesz nazwanych instancji. Prześlę tutaj ten link, gdy go znajdę ponownie.
W rzeczywistości nie musisz przekazywać tego Selektora ani Akcesorium:
Korzystam z następującego kodu w moim projekcie i do tej pory działał dobrze.
źródło
Stworzyłem do tego bibliotekę, która implementuje kilka fajnych funkcji. Kod można znaleźć na GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
Użycie jest proste:
W powyższym przykładzie zauważ, że dla każdej nazwanej rejestracji określasz także czas życia lub Singleton, Scoped lub Transient.
Możesz rozwiązać usługi na jeden z dwóch sposobów, w zależności od tego, czy czujesz się komfortowo, aby twoje usługi zależały od tego pakietu:
lub
Specjalnie zaprojektowałem tę bibliotekę, aby dobrze współpracowała z Microsoft.Extensions.DependencyInjection - na przykład:
Kiedy rejestrujesz nazwane usługi, każdy rejestrowany typ może mieć konstruktora z parametrami - będą spełniane przez DI, w taki sam sposób
AddTransient<>
, jakAddScoped<>
iAddSingleton<>
metody działają normalnie.W przypadku nazwanych usług przejściowych i o określonych zakresach rejestr jest budowany,
ObjectFactory
aby w razie potrzeby bardzo szybko aktywować nowe wystąpienia tego typu. Jest to o wiele szybsze niż w przypadku innych podejść i zgodne z tym, jak działa Microsoft.Extensions.DependencyInjection.źródło
Moje rozwiązanie za to, co jest warte ... rozważałem przejście na Castle Windsor, ponieważ nie mogę powiedzieć, że podobało mi się którekolwiek z powyższych rozwiązań. Przepraszam!!
Twórz różne wdrożenia
Rejestracja
Konstruktor i użycie instancji ...
źródło
Rozszerzenie rozwiązania @rnrneverdies. Zamiast ToString () można również użyć następujących opcji: 1) Z implementacją wspólnej właściwości, 2) Usługa usług sugerowana przez @Craig Brunetti.
źródło
Po przeczytaniu odpowiedzi tutaj i artykułów gdzie indziej udało mi się sprawić, że działał bez żadnych zobowiązań. Gdy masz wiele implementacji tego samego interfejsu, DI doda je do kolekcji, dzięki czemu możliwe będzie pobranie żądanej wersji z kolekcji za pomocą
typeof
.źródło
var serviceA = new ServiceA();
Chociaż implementacja tego nie oferuje, tutaj jest przykładowy projekt, który pozwala zarejestrować nazwane wystąpienia, a następnie wstrzyknąć INamedServiceFactory do kodu i wyciągnąć wystąpienia według nazwy. W przeciwieństwie do innych rozwiązań fakultatywnych tutaj, możesz zarejestrować wiele instancji tej samej implementacji, ale skonfigurowanych inaczej
https://github.com/macsux/DotNetDINamedInstances
źródło
A co z usługą dla usług?
Gdybyśmy mieli interfejs INamedService (z właściwością .Name), moglibyśmy napisać rozszerzenie IServiceCollection dla .GetService (nazwa ciągu), w którym rozszerzenie wziąłoby ten parametr ciągu i wykonałoby .GetServices () na sobie i za każdym razem zwracane instancja, znajdź instancję, której INamedService.Name pasuje do podanej nazwy.
Lubię to:
Dlatego twoja IMyService musi zaimplementować INamedService, ale uzyskasz pożądaną rozdzielczość kluczową, prawda?
Szczerze mówiąc, konieczność posiadania tego interfejsu INamedService wydaje się brzydka, ale jeśli chcesz pójść dalej i uczynić rzeczy bardziej eleganckimi, kod [NamedServiceAttribute („A”)] na implementacji / klasie może znaleźć kod w tym rozszerzenie i działałoby równie dobrze. Aby być jeszcze bardziej uczciwym, Refleksja jest powolna, więc może być optymalizacja, ale szczerze mówiąc, to jest coś, w czym silnik DI powinien był pomóc. Szybkość i prostota wnoszą ogromny wkład w TCO.
Podsumowując, nie ma potrzeby tworzenia jawnej fabryki, ponieważ „znalezienie nazwanej usługi” to koncepcja wielokrotnego użytku, a klasy fabryk nie skalują się jako rozwiązanie. I Func <> wydaje się w porządku, ale blok przełączników jest tak ponury i znowu będziesz pisał Funcs tak często, jak piszemy w Fabrykach. Zacznij od prostego, wielokrotnego użytku, z mniejszym kodem, a jeśli okaże się, że nie możesz tego zrobić dla ciebie, to idź skomplikowany.
źródło
Natrafiłem na ten sam problem i pracowałem z prostym rozszerzeniem, aby umożliwić usługi nazwane. Możesz go znaleźć tutaj:
Pozwala ci dodać tyle usług (nazwanych), jak chcesz:
Biblioteka umożliwia także łatwe wdrożenie „wzorca fabrycznego” w następujący sposób:
Mam nadzieję, że to pomoże
źródło
Utworzyłem własne rozszerzenie w stosunku do
IServiceCollection
używanegoWithName
rozszerzenia:ServiceCollectionTypeMapper
jest pojedyncza instancja że mapyIService
>NameOfService
>Implementation
gdzie interfejs może mieć wiele wdrożeń o różnych nazwach, pozwala zarejestrować typy niż możemy rozwiązać, gdy maleńki potrzeba i jest inne podejście niż usługi resolve wielu, aby wybrać to, co chcemy.Aby zarejestrować nową usługę:
Aby rozwiązać usługę, potrzebujemy takiego rozszerzenia
IServiceProvider
.Po rozwiązaniu:
Pamiętaj, że serviceProvider może zostać wstrzyknięty do konstruktora w naszej aplikacji jako
IServiceProvider
.Mam nadzieję, że to pomoże.
źródło