Posiadanie następującego konstruktora usługi
public class Service : IService
{
public Service(IOtherService service1, IAnotherOne service2, string arg)
{
}
}
Jakie są możliwości przekazywania parametrów przy użyciu mechanizmu .NET Core IOC
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));
Czy jest inny sposób?
Odpowiedzi:
Parametr wyrażenia ( w tym przypadku x ) delegata fabryki to plik
IServiceProvider
.Użyj tego, aby rozwiązać zależności,
_serviceCollection.AddSingleton<IService>(x => new Service(x.GetRequiredService<IOtherService>(), x.GetRequiredService<IAnotherOne>(), ""));
Delegat fabryki to opóźnione wywołanie. Zawsze, gdy typ ma zostać rozwiązany, przekaże ukończonego dostawcę jako parametr delegata.
źródło
.WithParameter("argument", "");
Microsoft.Extensions.DependencyInjection.Abstractions
pakietu (więc brak zależności od konkretnego kontenera)Należy zauważyć, że zalecanym sposobem jest użycie wzorca opcji . Ale są przypadki użycia, w których jest to niepraktyczne (kiedy parametry są znane tylko w czasie wykonywania, a nie w czasie uruchamiania / kompilacji) lub trzeba dynamicznie zastąpić zależność.
Jest to bardzo przydatne, gdy musisz zamienić pojedynczą zależność (czy to ciąg, liczbę całkowitą lub inny typ zależności) lub gdy używasz biblioteki innej firmy, która akceptuje tylko parametry typu string / integer i potrzebujesz parametru wykonawczego.
Możesz spróbować CreateInstance (IServiceProvider, Object []) jako skrótu
(nie jestem pewien, czy działa z parametrami ciągów / typami wartości / prymitywami (int, float, string), nietestowane)(Właśnie wypróbowałem i potwierdziłem, że działa, nawet z wiele parametrów łańcuchowych) zamiast rozwiązywać ręcznie każdą zależność:_serviceCollection.AddSingleton<IService>(x => ActivatorUtilities.CreateInstance<Service>(x, ""); );
Parametry (ostatni parametr z
CreateInstance<T>
/CreateInstance
) definiują parametry, które powinny zostać zastąpione (nierozpoznane przez dostawcę). Są one stosowane od lewej do prawej, gdy się pojawiają (tj. Pierwszy łańcuch zostanie zastąpiony pierwszym parametrem typu łańcuchowego typu, który ma być utworzony).ActivatorUtilities.CreateInstance<Service>
jest używany w wielu miejscach do rozwiązywania usługi i zastępowania jednej z domyślnych rejestracji dla tej pojedynczej aktywacji.Na przykład, jeśli masz klasę o nazwie
MyService
i maIOtherService
,ILogger<MyService>
jak zależnościami i chcesz rozwiązać usługę ale zastąpić domyślną usługęIOtherService
(powiedzmy ITSOtherServiceA
) zOtherServiceB
, można zrobić coś takiego:myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())
Wtedy pierwszy parametr
IOtherService
zostanieOtherServiceB
wstrzyknięty, a nieOtherServiceA
pozostałe parametry będą pochodzić z kontenera.Jest to przydatne, gdy masz wiele zależności i chcesz tylko traktować tylko jeden z nich (tj. Zastąpić konkretnego dostawcę bazy danych wartością skonfigurowaną podczas żądania lub dla określonego użytkownika, coś, co znasz tylko w czasie wykonywania i podczas żądania i nie podczas budowania / uruchamiania aplikacji).
Możesz także użyć metody ActivatorUtilities.CreateFactory (Type, Type []), aby zamiast tego utworzyć metodę fabryczną, ponieważ oferuje ona lepszą wydajność GitHub Reference i Benchmark .
Później jeden jest przydatny, gdy typ jest bardzo często rozpoznawany (na przykład w SignalR i innych scenariuszach z wysokim żądaniem). Zasadniczo utworzyłbyś
ObjectFactory
viavar myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });
następnie buforuj ją (jako zmienną itp.) i wywołuj ją w razie potrzeby
## Aktualizacja: po prostu wypróbowałem to sam, aby potwierdzić, że działa również z ciągami znaków i liczbami całkowitymi, i rzeczywiście działa. Oto konkretny przykład, który przetestowałem z:
class Program { static void Main(string[] args) { var services = new ServiceCollection(); services.AddTransient<HelloWorldService>(); services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow")); var provider = services.BuildServiceProvider(); var demoService = provider.GetRequiredService<DemoService>(); Console.WriteLine($"Output: {demoService.HelloWorld()}"); Console.ReadKey(); } } public class DemoService { private readonly HelloWorldService helloWorldService; private readonly string firstname; private readonly string lastname; public DemoService(HelloWorldService helloWorldService, string firstname, string lastname) { this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService)); this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname)); this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname)); } public string HelloWorld() { return this.helloWorldService.Hello(firstName, lastName); } } public class HelloWorldService { public string Hello(string name) => $"Hello {name}"; public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}"; } // Just a helper method to shorten code registration code static class ServiceProviderExtensions { public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => ActivatorUtilities.CreateInstance<T>(provider, parameters); }
Wydruki
źródło
.AddControllersAsServices
używany jest, który zastępujeControllerActivatorProvider
zServiceBasedControllerActivator
Jeśli nie podoba Ci się nowa usługa, możesz użyć
Parameter Object
wzoru.Więc wyodrębnij parametr ciągu do jego własnego typu
public class ServiceArgs { public string Arg1 {get; set;} }
A konstruktor teraz będzie wyglądał
public Service(IOtherService service1, IAnotherOne service2, ServiceArgs args) { }
I konfiguracja
_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; }); _serviceCollection.AddSingleton<IOtherService , OtherService>(); _serviceCollection.AddSingleton<IAnotherOne , AnotherOne>(); _serviceCollection.AddSingleton<IService, Service>();
Pierwszą korzyścią jest to, że jeśli musisz zmienić konstruktor usługi i dodać do niego nowe usługi, nie musisz zmieniać
new Service(...
wywołań. Kolejną korzyścią jest to, że konfiguracja jest nieco czystsza.Dla konstruktora z jednym lub dwoma parametrami może to być jednak za dużo.
źródło
Za pomocą tego procesu można również wstrzyknąć zależności
_serviceCollection.AddSingleton<IOtherService , OtherService>(); _serviceCollection.AddSingleton<IAnotherOne , AnotherOne>(); _serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));
źródło