Как использовать контейнер Autofac в качестве абстрактной фабрики? - PullRequest
0 голосов
/ 14 мая 2018

Я хотел бы изменить мою текущую реализацию LetterFactory и удалить вызов Activator.CreateInstance с помощью вызова контейнера, чтобы разрешить текущее письмо, полностью инициализированное инъекцией конструктора. Я прочитал документы здесь и здесь , и даже этот ТАК пост при написании этого поста, но, кажется, ничего не нажимает.

Примечания:

1) IDocumentServicesCore является агрегатом.

2) Все буквы украшены атрибутом LetterType (сотни из них)

3) Сам этот LetterFactory зарегистрирован в контейнере.

public class LetterFactory : ILetterFactory
{
    private readonly IDocumentServicesCore _documentServicesCore;

    public LetterFactory(IDocumentServicesCore documentServicesCore)
    {
        _documentServicesCore = documentServicesCore;
    }

    public LetterBase Create(int letterId)
    {
        if (letterId <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(letterId));
        }

        List<Type> types = typeof(LetterBase).Assembly.GetTypes()
            .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
            .ToList();

        LetterBase letter = null;
        foreach(Type type in types)
        {
            LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>().First();

            if (!attribute.LetterId.Contains(letterId))
            {
                continue;
            }

            letter = Activator.CreateInstance(type, _documentServicesCore) as LetterBase;
            break;
        }

        if (letter != null)
        {
            return letter;
        }

        string message = $"Could not find a LetterBase to create for id {letterId}.";
        throw new NotSupportedException(message);
    }
}

Update1

Проблемы, похоже, начинаются с того, что сами буквы не зарегистрированы, как мне взять код LINQ, который собирает буквы из сборки, и зарегистрировать эти enmass?

Спасибо, Стивен

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

Вы заставили меня сделать настоящую работу, хорошую работу :) Вот мое решение.

Autofac - именованные и ключевые службы - разрешение с помощью индекса

using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Autofac.Features.Indexed;


public class Program
{
    private static IContainer _Container;

    public static void Main()
    {
        InitDependencyInjection();  

        var rd1 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 1));
        rd1.PrintType();

        var rd2 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 2));
        rd2.PrintType();
    }

    private static void InitDependencyInjection()
    {
        var builder = new ContainerBuilder();

        var letterTypes = typeof(LetterBase).Assembly.GetTypes()
            // Find all types that derice from LetterBase
            .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
            // Make sure they are decorated by attribute
            .Where(t => 
              t.GetCustomAttributes(typeof(LetterTypeAttribute), false).Length == 1)
            .ToList();

        //Register with Autofac, Keyed by LetterId
        //This should throw an exception if any are duplicated
        //You may want to consider using an enum instead
        //It's not hard to convert an Int to Enum
        foreach(Type letterType in letterTypes)
        {
            // we already tested the type has the attribute above
            var attribute = letterType
              .GetCustomAttributes(typeof(LetterTypeAttribute)
                , false)[0] as LetterTypeAttribute;

            builder.RegisterType(letterType)
                .Keyed<LetterBase>(attribute.LetterId);
        }

        builder.RegisterType<RequiresDependency>();

        _Container = builder.Build();
    }

}

public class RequiresDependency
{
    private readonly LetterBase _letter;

    //Autofac automagically provides a factory that returns type
    //type you need via indexer
    public RequiresDependency(int letterId, IIndex<int, LetterBase> letterFactory)
    {
        //resolve the needed type based on the index value passed in
        _letter = letterFactory[letterId];
    }

    public void PrintType()
    {
        Console.WriteLine(_letter.GetType().Name);
    }
}

public abstract class LetterBase
{
}

[LetterType(1)]
public class LetterA : LetterBase
{}

[LetterType(2)]
public class LetterB : LetterBase
{}

// make sure the classes using this attribute has only a single attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LetterTypeAttribute : Attribute
{
    public LetterTypeAttribute(int letterId)
    {
        LetterId = letterId;
    }

    public int LetterId { get; private set; }
}

Пример DotNetFiddle

Результат:

Lettera

LetterB

0 голосов
/ 14 мая 2018

Вы ищете IIndex<TKey, TValue>, который является своего рода словарем, и он может быть составлен так, что IIndex<Int32, Func<LetterBase>> - это тот тип, который вам нужен.

С таким типом ваш LetterFactory будет выглядеть так:

public class LetterFactory : ILetterFactory
{
    private readonly IIndex<Int32, Func<LetterBase>> _lettersFactory; 
    public LetterFactory(IIndex<Int32, Func<LetterBase>> lettersFactory)
    {
        _lettersFactory = lettersFactory;
    }

    public LetterBase Create(int letterId)
    {
        if (letterId <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(letterId));
        }

        Func<LetterBase> letterFactory = null; 
        if(!this._lettersFactory.tryGetValue(letterId, out letterFactory))
        {
            string message = $"Could not find a LetterBase to create for id {letterId}.";
            throw new NotSupportedException(message);        
        }

        Letter letter = letterFactory(); 
        return letter; 
    }
}

И тогда вы должны зарегистрировать свои типы, как это:

List<Type> letterTypes = typeof(LetterBase).Assembly.GetTypes()
    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
    .ToList();

foreach(Type letterType in letterTypes)
{
    LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>()
                                        .First();

    builder.RegisterType(letterType)
           .Keyed<LetterBase>(attribute.LetterId);
}

Вы также улучшите производительность с помощью этого кода: сканирование тяжелых сборок будет происходить только один раз при запуске, а не для каждого вызова.

Кстати, помните об ограничении сканирования сборки в размещаемом приложении IIS: http://autofaccn.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications

Вы также можете напрямую положиться на IIndex<Int32, LetterBase> вместо IIndex<Int32, Func<LetterBase>>, это зависит от вашей стратегии охвата.

...