Имея следующий конструктор службы
public class Service : IService
{
public Service(IOtherService service1, IAnotherOne service2, string arg)
{
}
}
Каковы варианты передачи параметров с использованием механизма .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 >(), "" ));
Есть ли другой способ ?
Ответы:
Параметр выражения ( в данном случае x ) фабричного делегата - это
IServiceProvider
.Используйте это, чтобы разрешить зависимости,
_serviceCollection.AddSingleton<IService>(x => new Service(x.GetRequiredService<IOtherService>(), x.GetRequiredService<IAnotherOne>(), ""));
Делегат фабрики - это отложенный вызов. Когда когда-либо тип должен быть разрешен, он передаст завершенный поставщик в качестве параметра делегата.
источник
.WithParameter("argument", "");
Microsoft.Extensions.DependencyInjection.Abstractions
пакета (так что никаких зависимостей от контейнера)Следует отметить, что рекомендуется использовать шаблон параметров . Но есть случаи, когда это непрактично (когда параметры известны только во время выполнения, а не во время запуска / компиляции) или вам нужно динамически заменить зависимость.
Это очень полезно, когда вам нужно заменить одну зависимость (будь то строка, целое число или другой тип зависимости) или при использовании сторонней библиотеки, которая принимает только строковые / целочисленные параметры, и вам требуется параметр времени выполнения.
Вы можете попробовать CreateInstance (IServiceProvider, Object []) в качестве ярлыка
(не уверен, что он работает со строковыми параметрами / типами значений / примитивами (int, float, string), непроверенными)(просто попробовал и подтвердил его работу, даже с несколько строковых параметров) вместо того, чтобы разрешать каждую зависимость вручную:_serviceCollection.AddSingleton<IService>(x => ActivatorUtilities.CreateInstance<Service>(x, ""); );
Параметры (последний параметр
CreateInstance<T>
/CreateInstance
) определяют параметры, которые следует заменить (не разрешенные поставщиком). Они применяются слева направо по мере их появления (т. Е. Первая строка будет заменена первым строковым параметром типа, который должен быть создан).ActivatorUtilities.CreateInstance<Service>
используется во многих местах для разрешения службы и замены одной из регистраций по умолчанию для этой единственной активации.Например , если у вас есть класс с именем
MyService
, и она имеетIOtherService
,ILogger<MyService>
как зависимости , и вы хотите , чтобы решить эту услугу , но заменить службу по умолчаниюIOtherService
(говорят , что егоOtherServiceA
) сOtherServiceB
, вы могли бы сделать что - то вроде:myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())
Тогда
IOtherService
будетOtherServiceB
введен первый параметр , а неOtherServiceA
остальные параметры будут поступать из контейнера.Это полезно, когда у вас много зависимостей и вы хотите специально обработать один (например, заменить поставщика конкретной базы данных значением, настроенным во время запроса или для определенного пользователя, что вы знаете только во время выполнения и во время запроса и не когда приложение построено / запущено).
Вы также можете использовать ActivatorUtilities.CreateFactory (Type, Type []) Method для создания метода factory вместо этого, поскольку он обеспечивает лучшую производительность GitHub Reference и Benchmark .
Позже один полезен, когда тип разрешается очень часто (например, в SignalR и других сценариях с высоким уровнем запросов). В основном вы должны создать переходное
ObjectFactory
отверстиеvar myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });
затем кешируйте его (как переменную и т. д.) и вызывайте, где это необходимо
## Обновление: просто попробовал сам, чтобы убедиться, что он также работает со строками и целыми числами, и это действительно работает. Вот конкретный пример, который я тестировал:
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); }
Печать
источник
.AddControllersAsServices
не используется, который заменяетControllerActivatorProvider
сServiceBasedControllerActivator
ActivatorUtilities.CreateInstance()
именно то, что мне нужно. Благодарность!public string HelloWorld()
реализация метода отсутствовалаЕсли вам стало неудобно при обновлении услуги, вы можете использовать
Parameter Object
шаблон.Так что извлеките строковый параметр в его собственный тип
public class ServiceArgs { public string Arg1 {get; set;} }
А конструктор теперь будет иметь вид
public Service(IOtherService service1, IAnotherOne service2, ServiceArgs args) { }
И установка
_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; }); _serviceCollection.AddSingleton<IOtherService , OtherService>(); _serviceCollection.AddSingleton<IAnotherOne , AnotherOne>(); _serviceCollection.AddSingleton<IService, Service>();
Первое преимущество заключается в том, что если вам нужно изменить конструктор службы и добавить к нему новые службы, вам не нужно изменять
new Service(...
вызовы. Еще одно преимущество - установка немного чище.Однако для конструктора с одним или двумя параметрами это может быть слишком много.
источник