Разрешаване на автоматична фабрика от наименувана регистрация за общ тип

  1. Да приемем прост интерфейс:

    public interface ICommandHandler<T>
    {
        void Handle(T command);
    }
    
  2. Да приемем няколко реализации за различни конкретни T като:

    public class FooCommandHandler : ICommandHandler<FooCommand> { /*...*/ }
    
  3. Да приемем обща фабрична реализация на този интерфейс:

    public class FactoryCommandHandler<T> : ICommandHandler<T>
    {
        public FactoryCommandHandler(Func<ICommandHandler<T>> factory) { /*...*/ }
        /*...*/
    }
    

Сега искам да регистрирам манипулатора на фабриката от 3, за да бъде екземплярът, който се разрешава, когато ICommandHandler<T> се разрешава.
Проблемът ми е, че не успявам да регистрирам правилно другите реализации, така че фабрика за те могат да бъдат разрешени.

Ето какво опитах:

builder.RegisterAssemblyTypes(assembly)
       .Where(type => type.Name.EndsWith("CommandHandler"))
       .Named("concreteCommandHandler", typeof(ICommandHandler<>));


builder.RegisterGeneric(typeof(FactoryCommandHandler<>)
       .WithParameter(
             (p, c) => true,
             (p, c) => c.ResolveNamed("concreteCommandHandler", p.ParameterType))
       .As(typeof(ICommandHandler<>));

Това обаче се проваля, защото няма регистрирано име Func<ICommandHandler<SomeConcreteCommand>>. Autofac изглежда не може автоматично да създаде фабрика в този случай, която обикновено поддържа.

Как да коригирам регистрацията и да постигна целта си?


person Daniel Hilgarth    schedule 12.06.2013    source източник
comment
Каква е имплементацията на обекта FooCommand? Има ли родител? Ако го направи, можете да опитате да се регистрирате, като промените публичния клас FactoryCommandHandler‹T› : ICommandHandler‹T› където T : [parentclassname]   -  person Azhar Khorasany    schedule 12.06.2013
comment
@AzharKhorasany: Няма базов клас. И не виждам как ограничението за общ тип, което предлагате, променя нещо.   -  person Daniel Hilgarth    schedule 12.06.2013
comment
Прилагането на ограничение казва на компилатора, че обектите на T могат да бъдат само от конкретен тип и той трябва да може да се регистрира, когато без ограничението компилаторът не знае какво е T.   -  person Azhar Khorasany    schedule 12.06.2013
comment
@AzharKhorasany: Знам какво прави ограничението на типа. Въпросът ми няма нищо общо с компилатора. Това е въпрос за API на Autofac.   -  person Daniel Hilgarth    schedule 12.06.2013


Отговори (3)


За съжаление не можете да използвате RegisterAssemblyTypes в този случай - той не обработва напълно генерични регистрации по начина, по който трябва да го направите (трябва изрично да регистрирате всеки конкретен манипулатор на команда спрямо неговия внедрен интерфейс, включително общия тип на този интерфейс).

Вместо това можете да използвате следното като регистрация:

assembly.GetTypes()
    .Where(type => type.Name.EndsWith("CommandHandler"))
    .ToList()
    .ForEach(t => builder.RegisterType(t)
        .Named("concreteCommandHandler", typeof (ICommandHandler<>)
            .MakeGenericType(t.GetInterfaces()[0].GenericTypeArguments[0])
    ));

builder.RegisterGeneric(typeof(FactoryCommandHandler<>)
   .WithParameter(
         (p, c) => true,
         (p, c) => c.ResolveNamed("concreteCommandHandler", p.ParameterType))
   .As(typeof(ICommandHandler<>));

Това успешно ще ви позволи да направите нещо подобно, връщайки генеричната фабрика с наименуваната команда като параметър на конструктора:

container.Resolve<ICommandHandler<FooCommand>>().Handle(new FooCommand());
person Matt Davies    schedule 12.06.2013
comment
Благодаря много. Току-що разбрах, че RegisterAssemblyTypes е проблемът, след като написах моя собствен IRegistrationSource... Все още не бях разбрал как да го направя вместо това, благодаря много за това! - person Daniel Hilgarth; 12.06.2013
comment
Тествано и приемливо. Благодаря отново! Подобрих малко кода, който очертахте, моля, вижте отговора ми. - person Daniel Hilgarth; 12.06.2013

Съжалявам, че включвам Simple Injector тук, но не можах да не забележа, че се борите с нещо, което е детска игра в Simple Injector. В Simple Injector можете да правите каквото искате в два реда код:

// using SimpleInjector;
// using SimpleInjector.Extensions;

var container = new Container();

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    assembly);

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>), 
    typeof(FactoryCommandHandler<>));

Тези две прости линии гарантират следното:

  • Доставеното сглобяване се търси за конкретни реализации на ICommandHandler<T>.
  • Ако конкретна реализация дефинира интерфейса ICommandHandler<T> многократно, той се регистрира за всяка затворена обща версия на този интерфейс.
  • FactoryCommandHandler<T> е регистрирано да бъде обвито около ICommandHandler<T> реализации. За всяка затворена обща версия на ICommandHandler<T> се връща единичен екземпляр на тази обща FactoryCommandHandler<T>.
  • Func<ICommandHandler<T>> се инжектира в това FactoryCommandHandler<T>, което позволява създаването на декорирания (инстанцията, която е опакована). Това на практика забавя създаването на този екземпляр.
  • Инжектираната фабрика ще запази начина на живот на декорирания.

FactoryCommandHandler<T> зависи само от Func<T>, което е сингълтън. Следователно FactoryCommandHandler<T> може да бъде регистриран като сингълтон (какво се случва при регистрацията по-горе). Ако зависи от зависимости от други начини на живот, може би е по-добре да го регистрирате като преходно.

person Steven    schedule 12.06.2013
comment
Благодаря за коментара. Autofac също поддържа автоматични фабрики, базирани на делегати, без да е необходимо изрично да ги регистрирате. Въпреки това, методът RegisterSingleDecorator наистина е хубав, защото този факт, че липсва, е източникът на това сложно решение в Autofac. Когато реших да използвам Autofac преди няколко години, той изглеждаше този с най-чистия и прост синтаксис за регистрация. Но това наистина изглежда добре. Предполагам, че ще разгледам по-отблизо Simple Injector. - person Daniel Hilgarth; 13.06.2013
comment
Съгласен съм, че Autofac има наистина хубав и добър API. Simple Injector обаче е проектиран специално за типовете архитектури, с които се занимавате. Ето защо регистрацията е толкова лесна в този случай. За други сценарии, дизайни или архитектури Autofac може да е по-добър избор. - person Steven; 13.06.2013

Използвах кода в отговора на Мат Дейвис и го подобрих малко:

  • Сега той обработва правилно манипулатори на команди, които изпълняват други интерфейси.
  • Вече обработва правилно манипулатори на команди, които изпълняват ICommandHandler<T> многократно.
  • Подобрих оригиналната си версия, като коригирах първия параметър на WithParameter. По този начин сега поддържа множество параметри на конструктора на FactoryCommandHandler<T>.

Резултатът изглежда така:

public static class AutofacExtensions
{
    public static void RegisterGenericTypesWithFactoryDecorator(
        this ContainerBuilder builder, 
        IEnumerable<Type> relevantTypes, 
        Type factoryDecorator,
        Type implementedInterfaceGenericTypeDefinition)
    {
        var serviceName = implementedInterfaceGenericTypeDefinition.ToString();

        foreach (var implementationType in relevantTypes)
        {
            var implementedInterfaces =
                implementationType.GetGenericInterfaces(
                    implementedInterfaceGenericTypeDefinition);
            foreach (var implementedInterface in implementedInterfaces)
                builder.RegisterType(implementationType)
                       .Named(serviceName, implementedInterface);
        }

        builder.RegisterGeneric(factoryDecorator)
               .WithParameter(
                   (p, c) => IsSpecificFactoryParameter(p, implementedInterfaceGenericTypeDefinition), 
                   (p, c) => c.ResolveNamed(serviceName, p.ParameterType))
               .As(implementedInterfaceGenericTypeDefinition)
               .SingleInstance();
    }

    private static bool IsSpecificFactoryParameter(ParameterInfo p,
                                                   Type expectedFactoryResult)
    {
        var parameterType = p.ParameterType;
        if (!parameterType.IsGenericType ||
            parameterType.GetGenericTypeDefinition() != typeof(Func<>))
            return false;

        var actualFactoryResult = p.ParameterType.GetGenericArguments()
                                                 .First();
        if (actualFactoryResult == expectedFactoryResult)
            return true;
        if (expectedFactoryResult.IsGenericTypeDefinition && 
            actualFactoryResult.IsGenericType)
            return expectedFactoryResult == 
                   actualFactoryResult.GetGenericTypeDefinition();
        return false;
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> GetGenericInterfaces(
        this Type type, Type openGenericInterface)
    {
        return
            type.GetInterfaces()
                .Where(x => x.IsGenericType &&
                            x.GetGenericTypeDefinition() == openGenericInterface);
    }
}

Употреба:

var relevantTypes = assembly.GetTypes();
builder.RegisterGenericTypesWithFactoryDecorator(
    relevantTypes.Where(type => type.Name.EndsWith("CommandHandler")), 
    typeof(FactoryCommandHandlerDecorator<>), 
    typeof(ICommandHandler<>));
person Daniel Hilgarth    schedule 12.06.2013