понедельник, 23 июля 2012 г.

UnityContainer. Передача параметра конструктора в метод Resolve()

[UnityContainer. Pass constructor parameters to Resolve() method]

В одном своем проекте использую UnityContainer - расширяемый DI-контейнер (dependency injection container) от Misrosoft. И понадобилось мне передавать в конструкторы создаваемых контейнером сервисов объекты (DataContext), временем жизни которых я управлял сам. То есть мне понадобилось примерно следующее:

// работаем с одним контекстом данных
DataContext dx1 = container.Resolve();
container.Resolve(/*dx1*/); // конструктор сервиса: public Service1(DataContext dx)
container.Resolve(/*dx1*/); // конструктор сервиса: public Service2(DataContext dx)

// работаем с другим контекстом
DataContext dx2 = container.Resolve();
container.Resolve(/*dx2*/);

Оказалось, что для этой довольно типичной задачи UnityContainer не предлагает однозначного решения. Это обстоятельство порождает множество подходов, которые можно найти в интернете. Одним из таких решений является разрешение (resolve) объекта с переопределением его параметра:

DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve(new ParameterOverride("dx", dx1));

Но здесь нужно быть крайне осторожным, т.к. при изменении имени параметра, контейнер без какого-либо предупреждения инициализирует сервис им вновь созданным объектом DataContext, а наш объект будет попросту проигнорирован.

Многие другие найденные в интернете решения немного модернизированные базировались на указанном подходе. При этом имена параметров извлекались при помощи рефлексии, а реализация заворачивалась в метод расширение (extension).

Другой, приемлемый, на мой взгляд, подход - вынос инициализации сервисов контекстом в отдельный метод:

DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve();
Service2 srv2 = container.Resolve();
srv1.Init(dx1);
srv2.Init(dx1);

Я же задействовал потомка контейнера (ChildContainer), что, как мне показалось, является наиболее простым и естественным способом для решения поставленной задачи. Вот что у меня получилось:

DataContext dx1 = container.Resolve();
IUnityContainer childContainer = container.CreateChildContainer();
childContainer.RegisterInstance(dx1, new ExternallyControlledLifetimeManager()); // !!!
Service1 srv1 = childContainer.Resolve();
Service2 srv2 = childContainer.Resolve();
childContainer.Dispose();

Идея проста: В потомке контейнера регистрируем необходимую инстанцию DataContext, создаем с его помощью объект сервиса (Resolve) и в конце заметаем за собой следы (Dispose). Обратите внимание на указание внешнего контроллера времени жизни объекта dx1 (ExternallyControlledLifetimeManager) - тем самым мы исключаем DataContext из утилизации при вызове метода childContainer.Dispose().

Этот код лег в основу метода-расширения, которое я использовал в своем проекте:

public static class UnityExtensions
{
  public static T xResolve(this IUnityContainer container, DataContext dx) {
    using (IUnityContainer childContainer = container.CreateChildContainer())
    {
      childContainer.RegisterInstance(dx, new ExternallyControlledLifetimeManager());
      return childContainer.Resolve();
    }
  }
}

Теперь вызовы выглядят так:

DataContext dx1 = container.Resolve(); // Первая инстанция
Service1 srv1 = container.xResolve(dx1);
Service2 srv2 = container.xResolve(dx1);

DataContext dx3 = container.Resolve(); // Вторая инстанция
Service1 srv3 = container.xResolve(dx3);


PS: К концу написания статьи натолкнулся на еще один способ реализации необходимого функционала - использование переопределения зависимости DependencyOverride:

container.Resolve(new DependencyOverride(dx1));

Здесь объект DataContext встраивается в граф объектов контейнера и используется везде, где есть зависимость этого типа.

понедельник, 2 июля 2012 г.

Исключение файлов из списка копирования xcopy в build событиях Visual Studio (2010)

[Exclude files using xcopy in the build events of Visual Studio (2010)]

Для копирования файлов папки по pre-/post build событиям студии можно использовать команду командной строки - xcopy. Команда имеет множество параметров для самых различных сценариев, полный список которых можно посмотреть, набрав в интерпретаторе cmd “xcopy /?” или, например, здесь (http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/xcopy.mspx?mfr=true). Я, в процессе использования этой команды не имея большого опыта работы с командной строкой и ее инструкциями, столкнулся с рядом не совсем очевидных для меня проблем. Ниже их краткое описание и их решения.

Задача 1: Копирование обновленных файлов одной директории в другую после  построения проекта.

Моя команда копирования в “командной строке события после построения” (согласен, звучит сложно, но из песни слов не выкинешь - это официальное название  командной строки post-build событий в VisualStudio) с использованием макросов выглядела так:

xcopy "$(ProjectDir)SourceFiles" "$(ProjectDir)TargetFiles" /d /e /i

Здесь все выглядит довольно очевидно, но все же краткое описание используемых параметров:
/d - для копирования измененных файлов,
/e - копирование директорий и поддиректорий, даже если они пусты,
/i - будет создана папка назначения, если она не существует.

Замечание: Команда xcopy “чувствительна” к символу “\” в конце имени источника. Не стоит его подставлять, если источником копирования является папка. Команда завершится ошибкой:

xcopy "$(ProjectDir)SourceFiles\" "$(ProjectDir)TargetFiles" /d /e /i REM   (НЕВЕРНО)

Но для копирования всех файлов и под-папок директории можно использовать маску “*.*”. Следующая команда сработает как надо и эквивалентна первому рабочему варианту:

xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i

Задача 2. Исключение определенных файлов из списка копирования

Согласно справке для исключения файлов можно использовать параметр “/exclude” и его синтаксис в справке выглядит довольно просто:

/exclude:filename1[+[filename2]][+[filename3]] : Specifies a list of files containing strings.

Но, к сожалению, использование этой инструкции не так уж тривиально и вызвало у меня ряд затруднений. Например здесь filename1..N не имена исключаемых файлов, а имена файлов, которые содержат описания исключаемых файлов (по одному на каждую строку). Т.е. чтобы исключить файл “a.txt” нужно создать текстовый файл “excludeList.txt” в папке проекта и внести в него строку “a.txt”. Команда копирования файлов приобретает вид:

xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:$(ProjectDir)excludeList.txt

Но эта команда в ряде случаев не может быть выполнена и возвращает ошибку. Эта проблема часто связана с пустыми символами в пути к файлу, которые “разрывают” команду так, что она не может быть правильно интерпретирована. Поэтому мы и обрамили пути источника и цели в кавычки (“), но параметр /exclude не допускает их использования. Чтобы обойти эту проблему можно использовать относительный путь без кавычек, предварительно перейдя в нужную папку при помощи команды cd:

cd "$(ProjectDir)"
"$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:excludeList.txt

Теперь команда будет выполнятся, но исключаемый файл “a.txt” все равно окажется в целевой папке. Немножко помучившись выяснил (= случайно обнаружил), что первая строка файла excludeList.txt игнорируется интерпретатором и для успешного выполнения команды необходимо сместить описания на одну строчку ниже. Файл “excludeList.txt”:

[CR][LF]
a.txt”

На этом можно было бы поставить точку, но я не был удовлетворен тем, что описание исключаемых файлов скрыто от глаз в отельном файле. Мне  захотелось все иметь в одном месте. Для этого файл с описанием исключений можно создавать прямо в скрипте (здесь исключаются файлы “a.txt” и “b.txt”):

cd "$(ProjectDir)"
echo a.txt>EXCLUDE_LIST
echo b.txt>>EXCLUDE_LIST
REM или одной строкой: (echo a.txt&& echo b.txt)>EXCLUDE_LIST
xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:EXCLUDE_LIST

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

Замечание. В списке исключаемых файлов не допускается использование символов маски (такие как “*” или “?”), но сами значения, если выражаться “языком шаблонов поиска файлов” как бы обрамляются звездочкой. Например для записи “a.txt” будет происходить исключение файлов соответствующих маске “*a.txt*”.