Внедрение зависимостей .NET Core для многоуровневых множественных зависимостей реализации

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

Но как разрешить несколько многоуровневых зависимостей?

Например:

      public enum ShortEnum { Short, Long }

public interface ISomeValidator
{
    bool ValidateInputString(string str);
}

public class ConValidator : ISomeValidator
{
    public bool ValidateInputString(string str) => true;
}

public class DonValidator : ISomeValidator
{
    public bool ValidateInputString(string str) => false;
}

public class ConProvider : ISomeProvider 
{
    ISomeValidator conValidator; // Expects instance of ConValidator
    public ConProvider(ISomeValidator someValidator)
    {
        conValidator = someValidator;
    }
}

public class DonProvider : ISomeProvider 
{
    ISomeValidator donValidator; // Expects instance of DonValidator
    public DonProvider(ISomeValidator someValidator)
    {
        donValidator = someValidator;
    }
}

можно использовать как ключ. Это означает, что в зависимости от его значения либо ConProviderили же DonProviderвозвращается. Теперь провайдеры зависят от ISomeValidator, который, опять же, зависит от значения ключа ShortEnumможет быть разрешен как экземпляр ConValidatorили же DonValidator.

Другими словами, я хочу построить следующие два графа объектов:

      var provider1 = new ConProvider(new ConValidator());
var provider2 = new DonProvider(new DonValidator());

Как лучше всего использовать встроенный в .NET Core 3.1 механизм внедрения зависимостей?

2 ответа

Есть три способа добиться желаемого.

Первый вариант — вернуться к ручному подключению графов объектов путем регистрации делегата, который полностью составляет требуемые графы объектов, например:

      services.AddTransient<ISomeProvider>(
    c => new ConProvider(new ConValidator());
services.AddTransient<ISomeProvider>(
    c => new DonProvider(new DonValidator());

Это, однако, станет довольно громоздким, как только эти классы получат другие зависимости, потому что вы очень быстро начнете полностью обходить DI-контейнер.

Поэтому вместо этого, в качестве второго варианта, вы можете настроить контейнер таким образом, чтобы вам нужно было только newдва провайдера:

      // Note how these validators are -not- registered by their interface!
services.AddTransient<ConValidator>(); // Might have dependencies
services.AddTransient<DonValidator>(); // of its own

services.AddTransient<ISomeProvider>(c =>
    new ConProvider(
        someValidator: c.GetRequiredService<ConValidator>(),
        otherDependency1: c:GetRequiredService<IDependency1>());

services.AddTransient<ISomeProvider>(c =>
    new DonProvider(
        someValidator: c.GetRequiredService<DonValidator>(),
        otherDependency2: c:GetRequiredService<IDependency2>());    

Это более гибкое решение по сравнению с первым. Но тем не менее, изменения в конструкторах и заставляют вас обновлять их регистрации.

Для борьбы с этим в качестве третьего варианта можно использовать ActivatorUtilitiesучебный класс. Это поможет вам в достижении Auto-Wiring, когда контейнер определяет, какие зависимости требуются. Это может выглядеть так:

      services.AddTransient<ConValidator>();
services.AddTransient<DonValidator>();

services.AddTransient<ISomeProvider>(c =>
    ActivatorUtilities.CreateInstance<ConProvider>(
        c,
        c.GetRequiredService<ConValidator>());

services.AddTransient<ISomeProvider>(c =>
    ActivatorUtilities.CreateInstance<DonProvider>(
        c,
        c.GetRequiredService<DonValidator>());

ActivatorUtilities.CreateInstanceсоздаст для вас запрошенный класс; в этом случае либо ConProviderили же DonProvider. Он делает это, просматривая конструктор типа и разрешая все параметры конструктора из предоставленного cпоставщик услуг. Он разрешает все зависимости от поставщика услуг, за исключением зависимостей, которые вы указали вручную для CreateInstanceметод. В приведенном выше примере он будет соответствовать ISomeValidatorзависимость от поставляемого DonValidatorили же ConValidatorи вводить их вместо этого.

Если я правильно понял, вы хотите ConValidatorэкземпляр для ConProviderа также DonValidatorэкземпляр для DonProviderи провайдеры должны быть разрешены на ShortEnumценность.

мы можем изменить зависимости, как показано ниже

      public interface ISomeProvider
    {
        void Method1();
    }

    public interface ISomeValidator
    {
        bool ValidateInputString(string str);

    }

    public class ConValidator : ISomeValidator
    {
        public bool ValidateInputString(string str)
        {
            return true;
        }
    }

    public class DonValidator : ISomeValidator
    {
        public bool ValidateInputString(string str)
        {
            return false;
        }
    }

    public class ConProvider : ISomeProvider
    {
        ISomeValidator conValidator; // Expects instance of 
                                     //ConValidator
        public ConProvider(ValidatorResolver validatorResolver)
        {
            conValidator = validatorResolver(ShortEnum.Short);
        }

        public void Method1() {
            System.Console.WriteLine("Method1 COnProvider");
        }
    }

    public class DonProvider : ISomeProvider
    {
        ISomeValidator donValidator; // Expects instance of 
                                     //DonValidator
        public DonProvider(ValidatorResolver validatorResolver)
        {
            donValidator = validatorResolver(ShortEnum.Long);
        }

        public void Method1() {
            System.Console.WriteLine("Method1 DonProvider");
        }
    }

    public enum ShortEnum
    {
        Short,
        Long
    }

объявить делгейты

      public delegate ISomeProvider ProviderResolver(ShortEnum shortEnum);
public delegate ISomeValidator ValidatorResolver(ShortEnum shortEnum);

Потребительское обслуживание

       public class SomeOtherService
    {
        private readonly ISomeProvider _provider;
        public SomeOtherService(ProviderResolver providerResolver)
        {
            _provider = providerResolver(ShortEnum.Short);
            //OR
            _provider = providerResolver(ShortEnum.Long);
            _provider.Method1();
        }
    }

В ConfigureServicesметод StartUpучебный класс

      services.AddTransient<ConValidator>();
            services.AddTransient<DonValidator>();
            services.AddTransient<ConProvider>();
            services.AddTransient<DonProvider>();

            services.AddTransient<ProviderResolver>(serviceProvider => (shortEnum) =>
            {
                switch (shortEnum)
                {
                    case ShortEnum.Short:
                        return serviceProvider.GetService<ConProvider>();
                    case ShortEnum.Long:
                        return serviceProvider.GetService<DonProvider>();
                    default:
                        throw new KeyNotFoundException();
                }
            });

            services.AddTransient<ValidatorResolver>(serviceProvider => (shortEnum) =>
            {
                switch(shortEnum)
                {
                    case ShortEnum.Short:
                        return  serviceProvider.GetService<ConValidator>();
                    case ShortEnum.Long:
                        return serviceProvider.GetService<DonValidator>();
                    default:
                        throw new KeyNotFoundException();
                }
            });

            services.AddTransient<SomeOtherService>();
Другие вопросы по тегам