Проблема регистрации универсальных типов с помощью 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<,>));
Другие вопросы по тегам