Внедрение зависимостей в области видимости / переходном процессе, гарантирующее возвращение одного и того же экземпляра для интерфейса и реализации - PullRequest
0 голосов
/ 28 февраля 2019

Пожалуйста, не стесняйтесь предложить лучший заголовок для вопроса.Я не могу найти хорошее имя для описания проблемы.

Мне нужно было сделать класс доступным через внедрение зависимостей при запуске.Класс должен быть доступен не только через его конкретную реализацию, но и через интерфейс, который он реализует.И мне нужно убедиться, что это один и тот же экземпляр объекта, возвращаемый обеими инъекциями.

Сценарий реальных миров, который привел меня к одноэлементному случаю, представлял собой нюгет для абстракций, обеспечивающих интерфейс (IStore), несколько нюгетеров, содержащих конкретные реализации.(DBStore, RedisStore).Когда я пытался реализовать проверки работоспособности для каждой реализации магазина, я мог ввести IStore, но не конкретную реализацию.И я хотел использовать некоторые переменные, которые были инициализированы и изменены в конкретной реализации (поэтому мне нужен один и тот же экземпляр для обеих инъекций).Поскольку магазины (надеюсь) используются как синглтоны, это сработало.Я не говорю, что существует реальный сценарий для ограниченного и преходящего пути.Мне просто любопытно, если бы это было возможно, если бы они не были синглетонами.

Следующие коды описывают, как мне удалось сделать это с помощью синглетонов.

Способ, который привел меня к одноэлементному решению:

Имея этот интерфейс:

public interface ITestInterface
{
  string ReturnAString();
  int ReturnAnInt(); 
}

и эту конкретную реализацию

public class TestImplementation : ITestInterface
{
  private int counter = 0;
  public string ReturnAString() {return "a string"; }
  public int ReturnAnInt() { return counter++; }
}

Онииспользуется в двух (скажем) сервисах.Один хочет, чтобы интерфейс был внедрен в конструктор, а другому нужна конкретная реализация.

Метод проб и ошибок в методе Startup.ConfigureServices для внедрения одного и того же экземпляра в обоих случаях:

Попробуйте 1:

// only ITestInterface is injected but not TestImplemenation
services.AddSingleton<ITestInterface, TestImplementation>();

Попробуйте 2:

//only TestImplementation is injected (DI does not recognize it implements the Interface)
services.AddSingleton<TestImplementation>();

Попробуйте 3:

// both are injected but they are not singleton any more (counters increment independently)
services.AddSingleton<ITestInterface, TestImplementation>();
services.AddSingleton<TestImplementation, TestImplementation>();

Попробуйте 4:

TestImplementation instance = new TestImplementation();
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);
//services.AddSingleton<TestImplementation>(instance);

Ну, попробуйте 4У меня один и тот же экземпляр для обеих инъекций.

Теперь давайте предположим, что TestImplementation необходимо ввести некоторые (например, соединения) настройки.

Я могу написать метод расширения для получения настроек из конфигурации и пройтиэто к экземпляру синглтона.

TestImplementation instance = new TestImplementation(Configuration.GetTestSettings());
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);

Так как же мне добиться, чтобы обе инъекции были одним и тем же экземпляром, используя одни и те же настройки с областью действия или переходным процессом?Так как я не думаю, что смогу создать экземпляр вручную / кодом там.

Ответы [ 3 ]

0 голосов
/ 28 февраля 2019

Если я не ошибаюсь, Autofac способен делать то, что вы хотите, используя декораторы .

Однако добавление зависимости к третьемуparty lib часто нежелательна или даже запрещена из-за политики компании.

В таком случае, я думаю, вам лучше создать фабрику.Например:

public class TestFactory
{
    public ITestInterface Create(string flavor)
    {
        if (flavor == "concrete")
        {
            return new TestImplementation();
        }
        else
        {
            return new OtherTestImplementation();
        }
    }
}

А затем в вашем Startup.cs вы бы добавили его следующим образом:

services.AddSingleton<TestFactory>();

Обратите внимание, что, в конечном счете, срок службы службы ITestInterface будет зависетьгде вы будете хранить ссылку на результат вызова метода Create(...).

0 голосов
/ 28 февраля 2019

Используя Autofac , вы сможете добавить интерфейс и интерфейс реализации, используя метод AsSelf():

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf();

См. Этот ответ для получения дополнительной информации.объяснения.

В вашем случае - использовать его как синглтон с: SingleInstance().

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf().SingleInstance();
0 голосов
/ 28 февраля 2019

В основном вы хотите зарегистрировать один тип реализации сервиса как два сервисных контракта (конкретный класс + интерфейс).Это довольно распространенный случай, но, к сожалению, контейнер Microsoft DI по умолчанию (ServiceCollection) имеет ограниченную функциональность, и единственный способ добиться желаемого эффекта - использовать фабричные делегаты:

services.AddScoped<TestImplementation>();
services.AddScoped<ITestInterface>(s => s.GetRequiredService<TestImplementation>());

Хотя это и поможет.(за некоторые дополнительные затраты времени выполнения) я настоятельно рекомендую использовать один из полнофункциональных контейнеров DI, таких как Autofac или Ninject

...