Использование конструктора по умолчанию и параметризованного конструктора в единице C#

У меня есть приложение, которое использует единство FW для разрешения объектов во всем. Я сделал некоторые изменения в фреймворке и классах, которые можно увидеть в комментарии к коду как "NEW CHANGE"

Класс-обёртка выглядит так

public static class ContractResolver
{
        public static T Resolve<T>() //This is been used in many places in application
        {
            IUnityContainer container = new UnityContainer();
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers.Default.Configure(container);
            return container.Resolve<T>();
        }

        //NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
        public static T Resolve<T>(ParameterOverride[] parameterOverrides)
        {
            IUnityContainer container = new UnityContainer();
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers.Default.Configure(container);
            return container.Resolve<T>(parameterOverrides);
        }
}

Конфигурация выглядит так

  <unity>
    <containers>
      <container>
        <types>
          <type type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest"/>
        </types>
      </container>
    </containers>
  </unity>

Классы и интерфейс выглядят так

public interface IImageRepositoryService
{
    bool Exists(string imageName);
}

public class ImageRepositoryService : IImageRepositoryService
{
    private readonly string mFilterName = "StandardImageFilter";

    //[InjectionConstructor]
    public ImageRepositoryService()
    {
        DatabaseQueryProvider.Query("Image", mFilterName);
    }

    //NEW CHANGE. A CONSTRUCTOR THAT ACCEPTS A PARAMETER    
    //[InjectionConstructor]
    public ImageRepositoryService(string filterName)
    {
        mFilterName = filterName;
        DatabaseQueryProvider.Query("Image", filterName);
    }

    public bool Exists(string imageName)
    {
        Console.WriteLine("The image " + imageName + " found in filter " + mFilterName);
        return true;
    }

}

Использование выглядит как

var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
serviceDefault.Exists("myimage.bmp");

Новые изменения нарушают старое использование. т.е.

var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();

Выдает исключение Разрешение зависимости не удалось, type = "UnityTest.IImageRepositoryService", name = "(none)". Исключение произошло во время: при разрешении. Исключение: InvalidOperationException - тип String не может быть создан. Вы должны настроить контейнер для предоставления этого значения.

Мне бы хотелось, чтобы новые функциональные возможности при этом не хотели ломать старые функциональные возможности.

 var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
 serviceDefault.Exists("myimage.bmp");

Должно отображаться сообщение в консоли: "Изображение myimage.bmp найдено в фильтре StandardImageFilter"

var parameterOverride1 = new ParameterOverride("filterName", "filter1");
var servicefilter1 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride1 });
servicefilter1.Exists("myimage.bmp");

Должно отображаться сообщение в консоли "Изображение myimage.bmp найдено в фильтре filter1"

var parameterOverride2 = new ParameterOverride("filterName", "filter2");
var servicefilter2 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride2 });
servicefilter2.Exists("myimage.bmp");

Должно появиться сообщение в консоли: "Изображение myimage.bmp найдено в фильтре filter2"

Как решить эту проблему?

1 ответ

Решение

Если вы хотите разрешить тот же тип (в этом случае IImageRepositoryService), но имеют различные вызовы Resolve, вызывают разные конструкторы, тогда вам нужно будет использовать именованные регистрации.

В вашем случае вы можете сделать это в конфигурации XML:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <container>
    <register type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest">
      <constructor />
    </register>
    <register name="ParameterizedRepository" 
              type="UnityTest.IImageRepositoryService, UnityTest" 
              mapTo="UnityTest.ImageRepositoryService, UnityTest">
      <constructor>
        <param name="filterName" value="dummyValue" />
      </constructor>
    </register>
  </container>
</unity>

Обратите внимание, что я использовал стиль конфигурации Unity 2 (и 3).

Так что это говорит Unity, что при разрешении используется имя "ParameterizedRepository" для вызова конструктора с параметром "filterName". Я использовал фиктивное значение здесь, потому что мы все равно переопределим значение во время выполнения:

var imageRepositoryService = container.Resolve<IImageRepositoryService>(
    "ParameterizedRepository", 
    new ParameterOverride("filterName", "filter2"));

Так вот, как получить то, что вы хотите, используя Unity, поэтому с точки зрения вашего класса-обёртки вы должны добавить параметр name:

public static class ContractResolver
{
    //NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
    public static T Resolve<T>(string name, params ParameterOverride[] parameterOverrides)
    {
        IUnityContainer container = new UnityContainer();
        var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
        section.Containers.Default.Configure(container);
        return container.Resolve<T>(name, parameterOverrides);
    }
}

Несколько нежелательных комментариев (в духе попыток быть полезными):

  • Похоже, вы используете Unity версии 1. Если это так, вы можете рассмотреть возможность обновления (версия 3 была выпущена недавно), а если вы не используете Unity версии 1, то вы можете рассмотреть возможность изменения синтаксиса конфигурации XML для использования новый подход, а также с использованием LoadConfiguration() метод расширения.

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

  • Я могу понять, как вы будете склонны скрывать реализацию контейнера за ContractResolver но с добавлением ParameterOverride (которые специфичны для Unity), абстракция становится немного дырявой.

Другие вопросы по тегам