Continuous Integration: Трансформация App.config

17 мая 2013 г.

Скачать исходный код

При работе с корпоративными проектами нужно создавать приложения разного рода. Например, проект может содержать несколько веб-сайтов, WCF-сервисы, Silverlight-приложения, Windows-сервисы или консольные утилиты. Все эти приложения нужно будет включить в Continuous Integration, чтобы выпуск версий для тестового сервера и боевого производился в автоматическом режиме. У всех этих приложений есть файлы настроек, которые обычно хранятся в App.config или других XML-подобных файлах. Для разных версий приложений мы должны хранить свои строки подключения к БД, ссылки к сторонним сервисам, SMTP-хостам, задавать разные уровни логирования и т.п.

Мы уже рассмотрели, как работать с Web.config, как писать для него трансформацию, что такое трансформация и как она встраивается в процесс сборки проекта. Продолжим эту тему рассмотрением работы с App.config.

Создание App.config для каждой конфигурации

Для примера я создал два проекта: Консольное приложение и WPF приложение. В обоих проектах используются App.config и изначально они выглядят так:

В солюшене по-умолчанию есть две конфигурации и я сразу добавил еще одну (как добавлять конфигурацию мы рассматривали в статье Continuous Integration: Создание собственной конфигурации):

Для каждой конфигурации сборки мы создадим файл трансформации. Первое, что надо сделать - создать 3 файла: App.Debug.config, App.UAT.config, App.Release.config.

Теперь сделаем, чтобы эти файлы конфиугаций красиво отображались в дереве солюшена. Для этого откроем файл проекта в текстом редакторе и добавим в секцию с App.cofing XML-код:

ConsoleApp.csproj:

<ItemGroup>
  <None Include="App.config" />
  <None Include="App.Debug.config">
    <DependentUpon>App.config</DependentUpon>
  </None>
  <None Include="App.UAT.config">
    <DependentUpon>App.config</DependentUpon>
  </None>
  <None Include="App.Release.config">
    <DependentUpon>App.config</DependentUpon>
  </None>
</ItemGroup>

После обновления файла проекта Visual Studio отобразит наши файлы:

Описываем трансформацию в App.config

Мы создали файл трансформации для каждой конфигурации сборки. Для примера я добавил трансформации ключа в секции appSettings.

App.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="SmtpHost" value="debug-smtphost"/>
  </appSettings>
</configuration>

App.Debug.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add xdt:Transform="Replace" xdt:Locator="Match(key)" key="SmtpHost" value="debug-smtphost"/>
  </appSettings>
</configuration>

App.Release.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add xdt:Transform="Replace" xdt:Locator="Match(key)" key="SmtpHost" value="release-smtphost"/>
  </appSettings>
</configuration>

App.UAT.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add xdt:Transform="Replace" xdt:Locator="Match(key)" key="SmtpHost" value="uat-smtphost"/>
  </appSettings>
</configuration>

Обратите внимание на указание пространства имен: xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"

В примере мы используем Replace для замены всего нода в XML, но кроме полной замены TransformXml поддержиет и другие трасформации. Более подробно о них на MSDN.

Включаем трансформацию App.config

Для того, чтобы описанные трансформации начали применяться, нужно включить их в процесс сборки проекта. В файле проекта добавляем:

ConsoleApp.csproj:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="AfterBuild">
  <TransformXml Source="App.config" Transform="App.$(Configuration).config" Destination="App.config" StackTrace="true" />
</Target>

Мы использовали элемент UsingTask для подключения задач из сборки Microsoft.Web.Publishing.Tasks.dll. Вы можете скопировать эту сборку и связанные с ней к себе в проект, чтобы указать для подключения локальный путь, а саму сборку добавить в source control.

Переключим конфигурацию сборки в Release и запустим Build проекта. Мы видим, что в файл App.config были подставлены данные из App.Release.config:

Итоги

Включение трансформации для App.config осуществляется в 2 шага: добавить трансформации, включить трансформацию во время сборки проекта. Часть операций приходится делать вручную.

Таким же образом можно включить трансформации для любых XML-файлов.


Статья из серии Continuous Integration: Работа с Config-файлами:

  1. Continuous Integration: Трансформация Web.config
  2. Continuous Integration: Процесс сборки проекта и трансформации Config-файлов
  3. Continuous Integration: Создание собственной конфигурации
  4. Continuous Integration: Трансформация App.config
  5. Continuous Integration: Рефакторинг файлов конфигурации

22 комментария:

  1. Игорь Чакрыгин17 мая 2013 г. в 14:59

    http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5

    Неплохой экстеншн для автоматического создания файлов для разных конфигураций. А ещё, что более важно, умеет показывать файл после трансформации.

    И ещё вы не сталкивались с трансформацией Web.config для локальных проектов? У меня почему-то при запуске сайта через IIS Express трансформации не происходит. (Просто используется файл web.config).

    ОтветитьУдалить
  2. Александр Евсеев17 мая 2013 г. в 15:04

    Для добавления новых конфигураций я бы посоветовал использовать

    SlowCheetah - http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5, а для группировки файлов можно использовать NestIn - https://bitbucket.org/jfromaniello/nestin/

    ОтветитьУдалить
  3. Спасибо за расширения, надо будет посмотреть.


    Проблем с Express не было, вообще IIS не при чем при трансформации. Видимо в чем-то другом дело.

    ОтветитьУдалить
  4. Да, спасибо, надо будет посмотреть эти расширения.


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

    ОтветитьУдалить
  5. Хм, странно. Вот я создал пустой проект, сделал конфиги:

    Web.config:




    Web.Debug.config:




    Если запустить сайт в студии Ctrl+F5, то ConfigurationManager.AppSettings["Test"] даёт значение "Web.config", т.е. трансформаци не работают =(

    ОтветитьУдалить
  6. Дайте пожалуйста ссылку на код проекта

    ОтветитьУдалить
  7. Что делать, если приложение нужно разворачивать на десятки разных сайтов?

    ОтветитьУдалить
  8. Первое, что приходит в голову - сделать десяток трансформаций. Но условие можно сделать не по названию конфигурации $(Configuration), а в зависимости от параметра. Например, номера сайта или языка сайта.

    ОтветитьУдалить
  9. Спасибо за ссылку, посмотрел код.

    В файле проекта на AfterBuild вы не прописали вызов трансформации.

    <Target Name="AfterBuild">
    <TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
    </Target>

    Пройдите, пожалуйста, по шагам http://blog.byndyu.ru/2013/05/continuous-integration-webconfig.html

    ОтветитьУдалить
  10. О! Большое спасибо. Теперь работает =)

    ОтветитьУдалить
  11. У тебя работает трансформация в ClickOnce приложениях? У меня сама трансформация отрабатывает, но после деплоя приложения - ошибка "Файл, .exe.config, имеет рассчитанный хеш, отличный от указанного в манифесте.". Похоже, что трансформация запускается после того, как хэш конфига был посчитан. Не знаешь, как сделать. чтобы запускалась до?

    ОтветитьУдалить
  12. Нашел решение. Вот этот VS extension делает правильные изменения в .csproj файле, которые не портят ClickOnce сборку: http://visualstudiogallery.msdn.microsoft.com/579d3a78-3bdd-497c-bc21-aa6e6abbc859

    ОтветитьУдалить
  13. О, ты описал проблему и решение :) Спасибо!

    ОтветитьУдалить
  14. Решение без использования сторонних расширений:
    http://stackoverflow.com/a/5109530

    ОтветитьУдалить
  15. Сергей, я один в один решение описал.

    ОтветитьУдалить
  16. Различие в последнем шаге. У Вас:

    и Destination="App.config"
    В приведенном варианте:
    и

    Destination="$(IntermediateOutputPath)$(TargetFileName).config"

    У меня VS2010 и Вашем варианте я получаю "Could not write Destination file...". В VS2012 не пробовал.

    ОтветитьУдалить
  17. О, спасибо

    Видимо в примере кода к этой статье ошибся, в других уже поправил https://github.com/AlexanderByndyu/ContiniousIntegration-WebConfigAppConfigExamples/blob/master/RefactoringConfigExample/ConsoleApp/ConsoleApp.csproj

    ОтветитьУдалить
  18. Да пожалуйста. Спасибо и Вам за отличные статьи!

    Кстати, такая же заковырка в статье про трансформацию Web.config (Destination="Web.config"). В VS2010 получаю ошибку "Could not write Destination file...". Кроме того, в таком подходе при сборке в VS и при наличии TFS нужно постоянно делать Check Out для Web.config. Я нашел только такое решение:



    Добавил файлы Site.config, Site.Debug.config, Site.Release.config, а Web.config вынес из под Source Control.

    Есть ли лучшее решение?

    ОтветитьУдалить
  19. Тут есть несколько вариантов:
    - сделать основной файл Web.Common.config, а Web.config генерировать

    - для Web.config не делать трансформации в Debug и с ним работать на проекте. Остальные варианты сборки перенести на CI (TeamCity, TFS,...), там трансформации и коммитить не надо

    ОтветитьУдалить
  20. Нет проблемы с правами на файл? Например, когда я пытаюсь заменить файл трансформацией вылетает ошибка чтения файла (т.к. к сожалению tfs-2010 все файлы выставляет read-only). Помогает только копирование в файл с новым именем.

    ОтветитьУдалить
  21. Есть такая проблема в TFS, решали так:

    ОтветитьУдалить

Моя книга «Антихрупкость в IT»

Как достигать результатов в IT-проектах в условиях неопределённости. Подробнее...