Проблема регистрации универсальных типов с помощью Autofac в ASP.NET Core
Я относительно новый пользователь как Autofac, так и ASP.NET Core. Недавно я перенес небольшой проект из "классического" проекта ASP.NET WebAPI в ASP.NET Core. У меня проблемы с Autofac, особенно в регистрации универсальных типов.
В этом проекте используется шаблон Command, каждый обработчик команд является закрытым
public class UpdateCustomerCommandHandler: ICommandHandler<UpdateCustomerCommand>
Эти обработчики команд внедряются в контроллеры как:
readonly private ICommandHandler<UpdateCustomerCommand> _updateCustomerCommand;
public ValuesController(ICommandHandler<UpdateCustomerCommand> updateCustomerCommand)
{
_updateCustomerCommand = updateCustomerCommand;
}
Autofac настроен (частично) как:
var builder = new ContainerBuilder();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
//This doesn't seem to be working as expected.
builder.RegisterAssemblyTypes(assemblies)
.As(t => t.GetInterfaces()
.Where(a => a.IsClosedTypeOf(typeof(ICommandHandler<>)))
.Select(a => new KeyedService("commandHandler", a)));
Вышеприведенное, похоже, не регистрирует общий вид, как ожидалось. Если я использую метод регистрации ниже, он работает хорошо.
builder.RegisterType<UpdateCustomerCommandHandler>().As<ICommandHandler<UpdateCustomerCommand>>();
Когда я говорю "Это не работает", я имею в виду, что при попытке создать экземпляр контроллера я получаю "InvalidOperationException: невозможно разрешить службу для типа" BusinessLogic.ICommandHandler`1[BusinessLogic.UpdateCustomerCommand] "при попытке активировать "AutoFac_Test.Controllers.ValuesController".
Это хорошо работало в полной версии WebAPI этого проекта, но не после воссоздания его в ASP.NET Core. Чтобы было ясно, это работало отлично до портирования на ASP.NET Core.
Вот ссылка на код, который я использовал для воссоздания этой проблемы: https://dl.dropboxusercontent.com/u/185950/AutoFac_Test.zip
**** РЕДАКТИРОВАТЬ ПОСЛЕ РЕШЕНИЯ ОТКРЫЛ ****
На самом деле в моей конфигурации Autofac не было ничего плохого и, конечно, не самого Autofac. Произошло то, что я переименовал выходные данные моих зависимых сборок, чтобы выполнить сканирование компонентов сборки (замена AppDomain.CurrentDomain.GetAssemblies()
более элегантно, однако я никогда не изменял зависимости проекта API для ссылки на новые сборки. Поэтому Autofac сканировал правильно загруженные сборки, которые оказались более старыми версиями, которые не содержали интерфейсы и реализации, которые я ожидал...
2 ответа
Autofac имеет встроенную поддержку для регистрации закрытых типов open-generic.
builder
.RegisterAssemblyTypes(ThisAssembly)
.AsClosedTypesOf(typeof(ICommandHandler<>));
Это просканирует вашу сборку, найдет типы, закрывающие открытый универсальный ICommandHandler<>
интерфейс, и зарегистрируйте каждый из них на закрытый универсальный интерфейс, который они реализуют - в вашем случае, ICommandHandler<UpdateCustomerCommand>
,
Что не работает в вашем примере, так это то, что вы связываете ключ с вашими услугами. Autofac не ищет ключевую версию вашего ICommandHandler<UpdateCustomerCommand>
при попытке создать экземпляр ValuesController
Вот почему вы получаете исключение.
Изменить после комментария QuietSeditionist:
Я постараюсь немного рассказать о службах с ключами и службами по умолчанию. Вы зарегистрировали свои обработчики, связав commandHandler
ключ к ним.
Это означает, что, как только контейнер собран, вот единственный способ разрешить такой обработчик:
// container will look for a registration for ICommandHandler<UpdateCustomerCommand> associated with the "commandHandler" key
container.ResolveKeyed<ICommandHandler<UpdateCustomerCommand>>("commandHandler");
При создании экземпляра ValuesController
, Autofac не ищет ключ регистрации ICommandHandler<UpdateCustomerCommand>
потому что это не было предложено.
Эквивалентный код, который он выполняет, - и вы можете попробовать запустить этот код самостоятельно, чтобы получить исключение:
// BOOM!
container.Resolve<ICommandHandler<UpdateCustomerCommand>>();
Причина вашей второй регистрации в том, что вы не указали ключ в службе:
// No key
builder
.RegisterType<UpdateCustomerCommandHandler>()
.As<ICommandHandler<UpdateCustomerCommand>>();
// commandHandler key
builder
.RegisterType<UpdateCustomerCommandHandler>()
.Keyed<ICommandHandler<UpdateCustomerCommand>>("commandHandler");
Но так как вы не хотите регистрировать все ваши обработчики один за другим, вот как их зарегистрировать, не вводя их с клавиатуры:
builder
.RegisterAssemblyTypes(ThisAssembly)
.AsClosedTypesOf(typeof(ICommandHandler<>));
/Редактировать
Я вижу два сценария, где службы ключей могут быть полезны:
У вас есть несколько типов, реализующих один и тот же интерфейс, и вы хотите внедрить разные реализации в разные сервисы. Допустим, вы регистрируете оба
SqlConnection
а такжеDB2Connection
какIDbConnection
, Затем у вас есть 2 службы, одна из которых предназначена для SQL Server, а другая - DB2. Если они оба зависят отIDbConnection
, вы хотите убедиться, что вы вводите правильный в каждом сервисе.Если вы используете декораторы, регистрация работает так, как вы определяете сервисы, к которым декораторы будут обращаться по ключу - первый пример не требует пояснений.
Поскольку Google выводит вас на эту страницу, даже когда вы пытаетесь вручную зарегистрировать типы, я подумал, что, хотя это не отвечает на заданный вопрос, это будет полезно для будущих посетителей. Итак, если вы хотите вручную зарегистрировать универсальный тип, вы должны использовать этот формат:
service.AddTransient(typeof(IThing<>), typeof(GenericThing<>));
или если нет интерфейса, то просто:
service.AddTransient(typeof(GenericThing<>));
и для полноты, если у вас есть универсальный с несколькими типами:
services.AddTransient(typeof(GenericThing<,>));