|
Скачать исходный код |
Мы рассмотрели основы трансформации Web.config. Теперь стоит углубится в тему сборки проекта и рассмотреть детально, когда и как делается трансформация файлов конфигурации.
Я считаю, что без детального понимания процесса сборки и трансформации, мы будем использовать эти инструменты не достаточно эффективно и можем не видеть причин возникающих ошибок.
Как происходит сборка проекта
Файл проекта представляет XML-код для утилиты MSBuild. Предназначение у этой утилиты схоже с NAnt и Psake, но в отличие от них MSBuild используется в по-умолчанию в Visual Studio. Когда в VS мы нажимаем Build, фактически в MSBuild запускается наш файл проекта.
Для лучшего понимания стоит изучить основы работы с MSBuild в книге Inside the Microsoft® Build Engine: Using MSBuild and Team Foundation Build.
Из чего состоит файл проекта в Visual Studio
Рассмотрим основные части файла проекта. Из кода убраны повторяющиеся и неважные части.
MvcApplication.csproj:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\AuthConfig.cs" />
<Compile Include="App_Start\BundleConfig.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Content\themes\base\images\ui-bg_flat_0_aaaaaa_40x100.png" />
<Content Include="Content\themes\base\images\ui-bg_flat_75_ffffff_40x100.png" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
<ProjectExtensions>
</ProjectExtensions>
<Target Name="AfterBuild">
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
</Target>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</Project>
В начале XML-кода мы видим, что первым будет запущен DefaultTargets, который называется Build:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Этот target определен в Microsoft.Common.targets, как:
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
Returns="$(TargetPath)" />
В свойстве BuildDependsOn видно основную последовательность сборки проекта. BeforeBuild и AfterBuild мы будем определять сами, как, например, в прошлый раз в AfterBuild мы добавляли TransformXml. Стоит посмотреть на последовательность выполнения CoreBuild, которые определены в том же файле:
<PropertyGroup>
<CoreBuildDependsOn>
BuildOnlySettings;
PrepareForBuild;
PreBuildEvent;
ResolveReferences;
PrepareResources;
ResolveKeySource;
Compile;
ExportWindowsMDFile;
UnmanagedUnregistration;
GenerateSerializationAssemblies;
CreateSatelliteAssemblies;
GenerateManifests;
GetTargetPath;
PrepareForRun;
UnmanagedRegistration;
IncrementalClean;
PostBuildEvent
</CoreBuildDependsOn>
</PropertyGroup>
<Target
Name="CoreBuild"
DependsOnTargets="$(CoreBuildDependsOn)">
Здесь мы видим более детальную последовательность вызовов всех этапов сборки нашего проекта. Каждый из этих этапов можно посмотреть в файлах *.targets.
Дальше в файле проекта идет определение настроек для каждой конфигурации сборки. В нашем случае есть только две конфигурации:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
После этого определяются ItemGroup'ы для ссылок на сторонние сборки, файлы для компиляции и контент проекта.
<ItemGroup>
<Compile Include="App_Start\AuthConfig.cs" />
<Compile Include="App_Start\BundleConfig.cs" />
После этого идет основная часть, в которой содержатся все определения для нашего проекта. Подключаются Microsoft.CSharp.targets (внутри него подключается знакомый нам Microsoft.Common.targets) и Microsoft.WebApplication.targets:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
MSBuild, как основной инструмент сборки
Мы увидели, что наши проекты это просто код, который выполняется утилитой MSBuild. Любой Target можно переопределить или дополнить своим кодом. В основном изменениям подвергаются BeforeBuild и AfterBuild.
Что такое Build, Rebuild, Clean?
Мы все пользуемся этими тремя функциями в Visual Studio:
Что значит Build мы только что подробно рассмотрели. Теперь остановимся на Rebuild и Clean. Как можно было догадаться это обычные target'ы, которые определены в Microsoft.Common.targets. Когда мы выбираем в меню команду Rebuild, то MSBuild запускает не DefaultTargets, а Rebuild. Аналогично происходит с Clean.
Clean определен следующим образом:
<PropertyGroup>
<CleanDependsOn>
BeforeClean;
UnmanagedUnregistration;
CoreClean;
CleanReferencedProjects;
CleanPublishFolder;
AfterClean
</CleanDependsOn>
</PropertyGroup>
<Target
Name="Clean"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(CleanDependsOn)" />
Если посмотреть по реализации каждого из target'ов, то мы увидим, что отчищаются все промежуточные файлы и результаты сборок проекта.
Rebuild определен в том же файле:
<PropertyGroup>
<_ProjectDefaultTargets Condition="'$(MSBuildProjectDefaultTargets)' != ''">$(MSBuildProjectDefaultTargets)</_ProjectDefaultTargets>
<_ProjectDefaultTargets Condition="'$(MSBuildProjectDefaultTargets)' == ''">Build</_ProjectDefaultTargets>
<RebuildDependsOn>
BeforeRebuild;
Clean;
$(_ProjectDefaultTargets);
AfterRebuild;
</RebuildDependsOn>
</PropertyGroup>
<Target
Name="Rebuild"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(RebuildDependsOn)"
Returns="$(TargetPath)"/>
Фактически Rebuild является комбинацией из Clean и Build.
Основные директории *.targets
Вы можете сами увидеть и изучить все основные target'ы, которые уже определены и реализованы в папках:
- %programfiles%\MSBuild
- %windir%\Microsoft.NET\Framework\версия
Процесс трансформации Web.config
В примере с трансформацией Web.config мы добавили следующий код в AfterBuild:
MvcApplication.csproj:
...
<Target Name="AfterBuild">
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
</Target>
...
Выше мы рассмотрели, что AfterBuild вызывается в самом конце сборки проекта.
Задача TransformXml
Рассмотрим где и как определена задача TransformXml. Мы импортировали файл Microsoft.WebApplication.targets, который в свою очередь импортирует Microsoft.Web.Publishing.targets. В последнем и определена наша задача:
<UsingTask TaskName="TransformXml" AssemblyFile="Microsoft.Web.Publishing.Tasks.dll"/>
Посмотрим внутренности сборки Microsoft.Web.Publishing.Tasks.dll через dotPeek. У нас есть стандартный класс, который унаследован от Task. В методе Execute видим:
namespace Microsoft.Web.Publishing.Tasks
{
public class TransformXml : Task
{
// ...
public override bool Execute()
{
// ...
document = this.OpenSourceFile(this.Source);
xmlTransformation = this.OpenTransformFile(this.Transform, logger);
flag = xmlTransformation.Apply((XmlDocument) document);
// ...
}
}
Идем дальше и смотрим реализацию метода Apply и доходим до TransformLoop и дальше корни уходят в сборку Microsoft.Web.XmlTransform.dll:
namespace Microsoft.Web.XmlTransform
{
public class XmlTransformation : IServiceProvider, IDisposable
{
private void TransformLoop(XmlNodeContext parentContext)
{
foreach (XmlNode xmlNode in parentContext.Node.ChildNodes)
{
XmlElement element = xmlNode as XmlElement;
if (element != null)
{
XmlElementContext elementContext = this.CreateElementContext(parentContext as XmlElementContext, element);
// ...
this.HandleElement(elementContext);
// ...
}
}
}
}
}
Получается, что команда TransformXml по определенным XPath выполняет нужные нам трансформации.
Вызов TransformXml в файле проекта
Мы видели, как трансформация работает внутри, теперь будет довольно легко разобраться с самим вызовом:
MvcApplication.csproj:
<Target Name="AfterBuild">
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
</Target>
Исходный файл Source, который мы будем изменять, выставлен в значение Web.config. MSBuild будет ссылаться на директорию проекта, когда начнет искать этот файл.
Описание трансформации Transform выставлено в Web.$(Configuration).config. Переменная $(Configuration) выставляется в зависимости от типа конфигурации, выбранной в Visual Studio, либо в Microsoft.Common.targets:
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
Получается, что если в VS мы выбрали конфигурацию Debug для сборки проекта, то значение Transform будет равно Web.Debug.config.
Преобразованный файл мы записываем в Destination, в нашем случае это Web.config.
Создание своих Task для MSBuild
Вы можете создавать свои задачи по примеру TransformXml. Для этого достаточно создать класс, унаследованные от Task и подключить задачу через UsingTask.
Для многих задач уже есть готовые наборы Task'ов. Кроме них, я рекомендую использовать наборы от сообщества разработчиков с открытым исходным кодом:
- http://msbuildextensionpack.codeplex.com - The MSBuild Extension Pack provides a collection of over 480 MSBuild Tasks, MSBuild Loggers and MSBuild TaskFactories
- https://github.com/loresoft/msbuildtasks - The MSBuild Community Tasks Project is an open source project for MSBuild tasks
Всё в целом
Мы подробно рассмотрели процесс сборки проекта, чтобы он не был черным ящиком с магической кнопкой Build на панеле задач. Исходя из процесса, рассмотрели когда и как срабатывает функция TransformXml, которая трансформирует Web.config.
Как можно увидеть предложенный способ трансформации является одним из возможных, т.к процесс сборки построен очень гибко и доступен для расширения.
Статья из серии Continuous Integration: Работа с Config-файлами:
Спасибо. Оказывается всё просто :-)
ОтветитьУдалить