Как внедрить реализацию нескольких интерфейсов класса обслуживания одного интерфейса в классе, используя основной контейнер DI asp.net - PullRequest
0 голосов
/ 21 декабря 2018

Мы переносим проект из .Net Framework 4.6.2 в .Net Core 2.0.У меня есть ситуация, когда один интерфейс реализуется несколькими классами, все эти реализации должны быть внедрены в потребляющий клиент и выполняться в некотором порядке (здесь порядок и имя класса выбираются из конфигурации во время выполнения).Я внедряю IServiceProvider для разрешения этих реализаций в конструкторе потребляющего класса, но получаю исключение StackOverflowException при построении объектов.

Предостережение - все эти классы реализуют один интерфейс, и их экземпляры должны быть внедрены в потребляющий класс.Контейнер DI по умолчанию, предоставляемый .Net Core 2.0, не поддерживает именованное (имя класса) или типизированное внедрение (тип класса) для нескольких классов, реализующих один и тот же интерфейс, что поддерживается другими параметрами DI, такими как Unity (наш предыдущий контейнер).

Чтобы решить эту проблему, я внедряю IServiceProvider и помещаю его в другой класс, который создает все экземпляры, а затем фильтрует требуемый экземпляр на основе типа класса или имени класса.

Однако, когда я пытаюсь построить все этиВ некоторых случаях среда выполнения генерирует исключение StackOverFlowException.

Примечание. - Все классы регистрируются в Transient Scope, и создание отдельного интерфейса для каждого класса и их внедрение не допускается.

Моя оболочка IServiceProvider -

public class TypedImplementationDependencyInjector
{
    private readonly IServiceProvider _serviceProvider = null;

    public TypedImplementationDependencyInjector(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public T ResolveImplementation<U, T>() where T : class, U
    {
        if (typeof(U).IsInterface)
        {
            var services = _serviceProvider.GetServices<U>();
            var typedImplementation = services.FirstOrDefault(o => o.GetType() == typeof(T)) as T;
            return typedImplementation;
        }

        return default(T);
    }

public T ResolveImplementation<T>(Type type)
    {
        if (type == null)
            throw new TypeLoadException("Supplied type was null or empty");

        if (typeof(T).IsInterface)
        {   
            var services = _serviceProvider.GetServices<T>();
            var typedImplementation = services.FirstOrDefault(o => o.GetType() == type);
            return typedImplementation;
        }

        return default(T);
    }

    public T ResolveImplementation<T>(string assemblyName, string namespacename, string classname)
    {
        if (typeof(T).IsInterface)
        {
            Type type = Type.GetType($"{namespacename}.{classname}, {assemblyName}", true, true);
            return ResolveImplementation<T>(type);
        }

        return default(T);
    }
}

Мой реализуемый общий интерфейс

public interface IEntityOperationCommand
{
    void Execute(EntityOperationModel operationModel);
}

Мой потребительский класс, который разрешает эти зависимости -

public class EntityOperationProvider
{
    private readonly IEntityOperationCommand _TWCommandPrepare = null;
    private readonly IEntityOperationCommand _TWCommandCreateDetails = null;
    private readonly IEntityOperationCommand _TWCommandPostCreation = null;
    private readonly IEntityOperationCommand _TWCommandRaiseEvents = null;
    private readonly IEntityOperationCommand _TWCommandNotification = null;
    private readonly TypedImplementationDependencyInjector _implementationDependencyInjector = null;

    private const string assemblyName = "__assembly_name__";
    private const string namespaceName = "__namespace__";


    public EntityOperationProvider(TypedImplementationDependencyInjector implementationDependencyInjector)
    {
        _implementationDependencyInjector = implementationDependencyInjector;

        _TWCommandPrepare = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "PrepareEntity");
        _TWCommandCreateDetails = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "CreateDetails");
        _TWCommandPostCreation = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "PostCreation");
        _TWCommandRaiseEvents = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "RaiseEvents");
        _TWCommandNotification = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "SendNotification");
    }
}

Когда вводится EntityOperationProvider, его конструктор пытается построитьЗависимости и броски свackOVerflowException.

Я ожидаю, что объект EntityOperationProvider будет создан и внедрен.

Примечание. Я не могу решить проблему, вызывающую StackOverFlowException, циклические зависимости отсутствуют (например, класс PrepareEntity, требующий объекта RaiseEvents иили наоборот) или самозависимости (скажем, CreateDetails пытается рекурсивно создать свой собственный объект).Буду признателен за любую помощь.

Также, если есть какой-нибудь элегантный способ внедрения типизированных зависимостей (например, атрибут Dependency в Unity), а не внедрения IserviceProivder, это также будет полезно.

1 Ответ

0 голосов
/ 21 декабря 2018

Если вам нужно вводить IServiceProvider, тогда есть проблема с дизайном, так как это запах кода.

Asp.Net Core DI позволяет регистрировать несколько реализаций интерфейса.Как таковая, она также позволяет внедрять эту коллекцию в зависимый класс через IEnumerable<IInterface> в зависимый конструктор

Например,

public EntityOperationProvider(IEnumerable<IEntityOperationCommand> commands) {
    //...
}

, который будет внутренне использовать IServiceProvider.GetServices<T>() extension

Оттуда это просто вопрос извлечения желаемой реализации

public class EntityOperationProvider {
    private readonly IEntityOperationCommand _TWCommandPrepare = null;
    private readonly IEntityOperationCommand _TWCommandCreateDetails = null;
    private readonly IEntityOperationCommand _TWCommandPostCreation = null;
    private readonly IEntityOperationCommand _TWCommandRaiseEvents = null;
    private readonly IEntityOperationCommand _TWCommandNotification = null;


    public EntityOperationProvider(IEnumerable<IEntityOperationCommand> commands) {            
        _TWCommandPrepare = commands.OfType<PrepareEntity>().FirstOrDefault();
        _TWCommandCreateDetails = commands.OfType<CreateDetails>().FirstOrDefault();
        _TWCommandPostCreation = commands.OfType<PostCreation>().FirstOrDefault();
        _TWCommandRaiseEvents = commands.OfType<RaiseEvents>().FirstOrDefault();
        _TWCommandNotification = commands.OfType<SendNotification>().FirstOrDefault();
    }

    //...
}
...