Чтобы зарегистрировать какой-либо сервис как для интерфейса, так и для реализации:
services.AddSingleton<IFoo, Foo>();
services.AddSingleton<Foo>(x => x.GetRequiredService<IFoo>());
Это гарантирует, что можно разрешить как IFoo
, так и Foo
, и что контейнер предоставит один и тот же экземпляр.
Я пытаюсь распространить эту концепцию на свой вариант использования:
Я написал библиотеку, которая выполняет сканирование сборки при запуске и автоматически регистрирует типы, реализующие IAnalyser
. В клиентском коде я хочу разрешить как IEnumerable<IAnalyser>
, так и FooAnalyser
.
Я не знаю, как выполнить регистрацию. Это то, что у меня есть до сих пор:
IEnumerable<Type> implementationTypes = ScanClientAssembliesForImplementationsOfIAnalyser();
foreach (var implementationType in implementationTypes)
{
services.AddSingleton(typeof(IAnalyser), implementationType); // (1) works
//services.AddSingleton(implementationType); // (2) incorrect
services.AddSingleton(x => /* ...?... */); // (3)
}
Регистрация в (1) позволяет мне правильно разрешить IEnumerable<IAnalyser>
.
Регистрация в (2) позволяет мне разрешить FooAnalyser
, но это будет другой экземпляр и, следовательно, неверный.
Я думаю, что правильный подход — зарегистрировать один и тот же экземпляр через фабрику. Как мне это сделать в (3)?
(Обратите внимание, что мой описанный выше подход может быть неправильным. Все, что я хочу, это разрешить оба IEnumerable<IAnalyser>
и FooAnalyser
в клиентском коде. Может быть, есть совершенно другой способ сделать это?)
Я нашел обходной путь, но он мне не нравится:
// allow resolution of `IEnumerable<IAnalyser>` // as before
services.AddSingleton(typeof(IAnalyser), implementationType);
// allow resolution of `FooAnalyser` // <-----
services.AddSingleton(implementationType, x => x
.GetRequiredService<IEnumerable<IAnalyser>>()
.Single(y => y.GetType().Equals(implementationType)));
Кажется, делать это для каждого разрешения очень дорого. (Хотя, если бы существовал какой-то встроенный способ делать то, что я хочу, он, вероятно, делал бы что-то подобное.)
Если вы заметите ошибку в моем подходе, пожалуйста, дайте мне знать. Если есть лучший способ, добавьте свой ответ, и я приму его.
Я нашел более простое решение.
Есть одна приятная вещь — когда вы регистрируете реализацию интерфейса, вы всегда (из коробки) можете разрешить IEnumerable<interface>
получить все зарегистрированные реализации.
Сказав это, мы могли бы просто перебрать все типы, зарегистрировав их как синглтон для типа, и зарегистрировать их как реализацию интерфейса, используя фабричный метод, как показано ниже:
// Registartion code
IEnumerable<Type> implementationTypes = ScanClientAssembliesForImplementationsOfIAnalyser();
foreach (var type in implementationTypes)
{
builder.Services.AddSingleton(type);
builder.Services.AddSingleton(typeof(IAnalyser), sp => sp.GetRequiredService(type));
}
У меня есть пример настройки:
public interface IAnalyser { }
public class Analyzer1 : IAnalyser { }
public class Analyzer2 : IAnalyser { }
И затем разрешение (с минимальным API, но оно, конечно, работает и с контроллерами):
app.MapGet("/weatherforecast", (IEnumerable<IAnalyser> analyzers, Analyzer1 a1, Analyzer2 a2) =>
{
var result = analyzers.ElementAt(0) == a1; // true
result = analyzers.ElementAt(1) == a2; // true
});
Вы поменяли порядок регистрации (зарегистрируйте реализацию, затем интерфейс). Решение было настолько очевидным, но я потратил на него так много времени, что не увидел его... понимаете, о чем я? :-) Гораздо лучше, чем мой подход - спасибо, Михал!