Статьи

Использование пользовательских преобразований Web.config в MSBUILD

Преобразования Web.config существуют уже давно, и многие разработчики используют их в своих основных стратегиях развертывания повседневной среды — черт, Скотт Хансельман говорил о них еще в начале 2010 года со своим « Вебом». Развертывание сделано потрясающе: если вы используете XCOPY, вы делаете это неправильно ». Как обычно, хотя один размер не подходит всем — и в случае поклонников Continuous Integration, у которых могут быть определенные сценарии сборки и развертывания на основе конфигурации сборки (такие как я), существует необходимость в более детальном контроле над процесс преобразования Web.config. Если это звучит как вы, то этот пост предназначен для доставки.

Что за секунда … что за чертовщина «Web.config Transforms»?

образВ ASP.net было несколько функций, которые существовали вечно, когда дело доходит до абстрагирования или чередования различных данных конфигурации для вашего сайта (я в основном говорю о функциональности configSource ). Функции были очень минимальными и обычно создавали далеко не идеальное решение для разработчиков, работающих на больших сайтах в разных средах. С появлением Visual Studio 2010 Microsoft любезно помогла нам всем, отметив, что « эй, может быть, не все веб-сайты создаются для одного сервера с единой конфигурацией »… Умные ребята . Они создали преобразования web.configЧтобы помочь справиться с этой проблемой и остальными шутками, эта функция на самом деле довольно классная и позволяет вам написать базовый файл web.config, как обычно, а затем преобразовать его для каждой из ваших сред.

Ваша база web.config:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="ExampleApplicationSetting" value="Value being replaced by Transform"/>
    </appSettings>
    <connectionStrings>
        <add name="MyConnectionString" connectionString="..." providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <customErrors mode="Off"/>
        <compilation debug="true">
        </compilation>
    </system.web>
</configuration>

Ваше преобразование web.config (обратите внимание на изменение строки подключения, пользовательских ошибок и режима компиляции):

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <appSettings>
        <add key="ExampleApplicationSetting"
                 xdt:Transform="SetAttributes(value)"
                 xdt:Locator="Condition(@key='ExampleApplicationSetting')"
                 value="The new value to replace after transform"/>
    </appSettings>
    <connectionStrings>
        <add name="MySolutionDatabase" xdt:Transform="Replace" xdt:Locator="Condition(@name='MySolutionDatabase')"
                 connectionString="... My New Connection String ..." providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <customErrors xdt:Transform="Replace" mode="RemoteOnly" />
        <compilation xdt:Transform="SetAttributes(debug)" debug="false" />
    </system.web>
</configuration>

Если вы впервые слышите о преобразованиях web.config, и синтаксис в моем примере выше не самый ясный, то вы найдете страницу MSDN о синтаксисе преобразования полезным чтением.

Так что же не так с использованием по умолчанию?

«Ну и что?»   Вы говорите: « Я использую их каждый день, и они работают нормально» . Основная проблема, которую я имею с «готовым решением», заключается в следующем;

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

При использовании Visual Studio и щелчке правой кнопкой мыши на файле web.config и Add Config Transformations вы получите следующее

 

образ

Для меня это большая проблема, так как почти во всех местах, где я работал, где используется непрерывная доставка или непрерывная интеграция с веб-сайтами ASP.Net, использовались конфигурации решений для переключения между различными средами сборки, а не конфигурации проекта.

Ваш веб-сайт должен иметь только две конфигурации сборки, которые вы используете для компиляции:

  • отлаживать
  • Выпуск

Ваша другая среда может быть:

  • Местный
  • Внутренняя постановка
  • Гарантия качества
  • Внешняя постановка
  • производство
  • ??

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

В некоторых средах вы можете использовать то же преобразование web.config

Еще одна проблема, с которой я столкнулся при этой настройке, заключается в том, что она предполагает, что у вас есть разные преобразования для каждой из ваших конфигураций сборки. Что делать, если вы используете одинаковые конфигурации среды для ряда ваших конфигураций сборки? Ваша производственная среда, вероятно, должна быть такой же, как ваша производственная среда, например — я не начинаю знать ваши требования, но я знаю, что мои не такие жесткие, как эта.

В некоторых средах вы можете использовать несколько уровней преобразований web.config, применяемых поверх друг друга.

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

образ

Так, где это оставляет нас?

Для меня это привело к тому, что я сильно хотел найти решение, которое могло бы дать мне такой уровень детализации, при этом все еще используя инструменты преобразования, встроенные в Visual Studio, и все приятные функциональные возможности, основанные на соглашениях, которые приходят с их использованием. До этого момента я был большим поклонником абстрагирования моих конфигов на основе среды, используя что-то похожее на приведенное ниже и меняя строку «\ Live \» во время сборки:

<configuration>
    <appSettings configSource ="Configs\Live\AppSettings.config" />
</configuration>

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

образ

«… MSBUILD это адский наркотик…»

Я большой поклонник MSBUILD при написании сценариев сборки, если только по той причине, что проекты Microsoft по веб-развертыванию в основном представляют собой сценарии MSBUILD, которые хорошо интегрируются в Visual Studio IDE.

Почему это важно? Потому что я собираюсь показать вам способ использовать MSBUILD для поэтапного применения преобразований web.config, позволяя вам применять их с такой высокой степенью детализации, как вам нравится …

OMFG WTG BBQ RU SERIUZZ!

Краткий ответ: да

Короткая версия

Применить преобразования конфигурации с помощью MSBUILD довольно просто, все, что вам нужно, это установить Visual Studio 2010 на компьютере, на котором выполняется сборка (в моем случае это обычно мой сервер TeamCity).

Пример минимального применения трансформации показан ниже ( не используйте его, пока не прочитаете оставшуюся часть этого поста и не узнаете об «ошибке») :

<UsingTask TaskName="TransformXml"
                        AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="TransformWebConfig">
    <TransformXml Source="C:\Code\MyProject\Web.config"
                  Transform="C:\Code\MyProject\TransformFile.config"
                  Destination="C:\Code\MyProject\Web.config"
                  StackTrace="true" />
</Target>

Мой приведенный выше пример как бы объясняет себя, но только для того, чтобы уточнить, что это делает:

  1. Импортирует сборку, найденную в $ (MSBuildExtensionsPath) \ Microsoft \ VisualStudio \ v10.0 \ Web \ Microsoft.Web.Publishing.Tasks.dll, и присваивает ей имя задачи TransformXml .
  2. Определяет новую цель с именем TransformWebConfig (чтобы ее можно было вызывать откуда-то еще в файле проекта развертывания MSBUILD / Web, например в разделе AfterBuild ).
  3. Внутри нашей вновь созданной цели мы вызываем TransformXml и передаем ей следующие параметры
    1. Наш исходный файл web.config находится по адресу C: \ Code \ MyProject \ Web.config
    2. Наш файл преобразования расположен в C: \ Code \ MyProject \ TransformFile.config
    3. Конечный файл web.config должен быть сохранен в C: \ Code \ MyProject \ Web.config
    4. Включите StackTrace, чтобы в моем журнале сборки можно было просматривать любые проблемы с преобразованием, такие как правила, которые не могли быть применены и т. Д. (Хорошо иметь на рассмотрении).

Довольно круто, а?

С помощью приведенного выше фрагмента вы можете применять столько разных преобразований, сколько вам нужно в ваших сценариях сборки, используя команду по-разному — даже для применения более сложных конфигураций, таких как применение многоуровневых преобразований.

Будь драконами! — Используйте это вместо

Известная проблема с вышеуказанной задачей MSBUILD заключается в том, что она не закрывает исходный файл web.config во время применения преобразования, что приводит к ошибке при чтении и записи в тот же файл web.config. Это очень раздражает, так как приводит к сбою сборки, если вы используете мой пример выше , потому что MSBUILD попытается переписать исходный файл web.config, пока он еще открыт. Чтобы преодолеть это, я добавляю простой второй шаг к моей задаче сборки, которая копирует файл web.config во временную папку, а затем удаляет файл после того, как преобразование выполнено.

<UsingTask TaskName="TransformXml"
                        AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="TransformWebConfig">
    <ItemGroup>
        <OriginalWebConfig Include="C:\Code\MyProject\Web.config"/>
        <TempWebConfig Include="C:\Code\MyProject\TempWeb.config"/>
    </ItemGroup>
    <Copy SourceFiles="@(OriginalWebConfig)" DestinationFiles="@(TempWebConfig)" />
    <TransformXml Source="C:\Code\MyProject\TempWeb.config"
                            Transform="C:\Code\MyProject\TransformFile.config"
                            Destination="C:\Code\MyProject\Web.config"
                            StackTrace="true" />
    <Delete Files="@(TempWebConfig)" />
</Target>

Более продвинутые техники

Как я упоминал ранее в этом посте, я обычно предпочитаю использовать более динамическое переключение на основе конфигурации решения в моих настройках Continuous Delivery для развертывания и поэтому хочу иметь возможность применять мои преобразования в зависимости от запуска конфигурации решения, чтобы обеспечить больше гибкости.

Моя обычная настройка проекта веб-сайта включает в себя папку с именем Deployment, которая находится в корне сайта. В эту папку я включаю любые активы, которые используются как часть моего развертывания . Это включает в себя такие вещи, как SFTP-клиент из командной строки, средство проверки web.config и любые файлы конфигурации, которые используются в моей сборке. У меня обычно есть шаг после сборки, который удаляет эту папку, так что вы не можете найти такие вещи, как хеши sftp или строки подключения к базе данных альтернативной среды, которые попадают на серверы вашего веб-хостинга — я настоятельно рекомендую вам выполнить аналогичную процедуру.

образ

Для моего примера ниже я основываю все свойства, которые я посылаю вышеописанной задаче сборки TransformXml, на параметрах конфигурации сборки и путях к файлам, которые используются во время сборки. Я также ссылаюсь на файлы преобразования, которые у меня есть в папке \ Deployment \, показанной выше.

Мой пример ниже, делает следующее:

  1. Определяет свойство с именем WebConfigReplacement и определяет его в зависимости от используемой конфигурации сборки решения. При построении промежуточного развертывания используйте файл промежуточного преобразования, если при производственном развертывании используется файл преобразования рабочей конфигурации.
  2. Скопируйте файл web.config во временную папку в моей папке \ Deployment \
  3. Запускает преобразование конфигурации, определенное на шаге 1, во временный файл web.config, созданный на шаге 2, и сохраняет его поверх моих веб-приложений web.config
  4. Удалите временный файл web.config в моей папке \ Deployment \ .

 

<!-- Setup the transformation file to use -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Deploy - Staging|AnyCPU'">
    <WebConfigReplacement>Staging</WebConfigReplacement>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Deploy - Production|AnyCPU'">
    <WebConfigReplacement>Production</WebConfigReplacement>
</PropertyGroup>

<UsingTask TaskName="TransformXml"
                        AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<PropertyGroup>
    <TransformInputFile>$(OutputPath)\Deployment\Web.Temp.config</TransformInputFile>
    <TransformFile>$(OutputPath)\Deployment\Web.$(WebConfigReplacement).config</TransformFile>
    <TransformOutputFile>$(OutputPath)\Web.config</TransformOutputFile>
    <StackTraceEnabled>False</StackTraceEnabled>
</PropertyGroup>
<ItemGroup>
    <OriginalWebConfig Include="$(OutputPath)\Web.config"/>
    <TempWebConfig Include="$(OutputPath)\Deployment\Web.Temp.config"/>
</ItemGroup>
<Target Name="TransformWebConfig"
                Condition="'$(Configuration)|$(Platform)' == 'Deploy - Production|AnyCPU' Or '$(Configuration)|$(Platform)' == 'Deploy - Staging|AnyCPU'">
    <!-- Copy our web.config into a temp folder as the 'TransformXml' task has a file lock bug -->
    <Copy SourceFiles="@(OriginalWebConfig)" DestinationFiles="@(TempWebConfig)" />

    <TransformXml Source="$(TransformInputFile)"
                                Transform="$(TransformFile)"
                                Destination="$(TransformOutputFile)"
                                StackTrace="$(StackTraceEnabled)" />
    <Delete Files="@(TempWebConfig)"/>
</Target>

В заключение

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