Visual Studio 2010 umožňuje provádět transformace XML pro soubory web.config. V praxi to funguje tak, že z jednoho výchozího souboru web.config a jednoho „rozdílového“ (např. web.debug.config nebo web.release.config apod.) s definicí transformací se při buildu vygeneruje soubor výsledný. Tyto transformace přitom nejsou založeny na XSLT a podobných XML technologiích, ale na technologii zcela nové. Ta má oficiální název XDT (XML Document Transforms) a více se o ní lze dočíst zde nebo např. zde.
Ačkoliv je XDT navrženo zcela obecně, a navíc musím říct, že poměrně dobře, ve VS2010 bohužel nelze XDT transformace použít na nic jiného než právě jen a pouze soubory web.config. A samotný XDT engine je zabudovaný přímo jako součást technologie MSBuild (přesněji jeho extensions), tj. ke své činnosti minimálně potřebuje mít právě nainstalované celé VS2010 a to je velká škoda.
Pro možnost využit XDT i ve vlastním .net projektu jsem proto vytvořil zcela samostatnou assembly nazvanou Microsoft.Xml.Transform (odkaz na stažení je na konci tohoto příspěvku). Jedná se tedy o klasickou .net knihovnu (pro .NET Framework 4 Client Profile), která umožnuje spouštět XDT transformace přímo v runtime běhu naší aplikace a na libovolné XML dokumenty až už ležících na disku nebo i přímo jen nad pamětí.
Scénářem použití přitom může být např. to, že v hlavní assembly (např. v resource) máme jedno výchozí XML a potom budeme mít několik různých “rozdílových” XML např. pro různé zákaznické customizace apod. Jiným příkladem může být volání transformace konfiguračních XML souborů jako součást automatizovaného update procesu naší aplikace.
Veřejné rozhraní knihovny umožňuje specifikovat vstupy/výstupy transformace a dále obsahuje svojí vlastní infrastrukturu pro logování běhu transformace. Její použití není příliš komplikované, ukážeme si ho na příkladu.
Příklad vytahuje zdrojový i transformační XML dokument uložený v aktuální assembly jako Embedded Resource a generuje výsledný XML dokument do objektu MemoryStream, jehož obsah je následně vypsán. Průběh transformace je přitom pro debug logován na standardní výstup.
using System;
using System.IO;
using Microsoft.Xml.Transform;
namespace TransformTest
{
static class Program
{
static void Main()
{
const string sourceResourceName = "TransformTest.Properties.Source.xml";
const string transformsResourceName = "TransformTest.Properties.Transforms.xml";
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
MemoryStream output = new MemoryStream();
var transform = new TransformXml();
#if DEBUG
transform.Logger = new AnonymousLogger(message => System.Diagnostics.Debug.WriteLine(message));
#endif
transform.Execute(new InputItem(assembly.GetManifestResourceStream(sourceResourceName), sourceResourceName),
new InputItem(assembly.GetManifestResourceStream(transformsResourceName), transformsResourceName),
new OutputItem(output, true /*leave open*/));
Console.WriteLine();
Console.WriteLine("Output XML:");
using (var reader = new StreamReader(new MemoryStream(output.GetBuffer(), 0, (int)output.Length)))
{
Console.WriteLine(reader.ReadToEnd());
}
}
}
}
Obsah zdrojového XML dokumentu Source.xml (uloženého v resource):
<configuration>
<connectionStrings>
<clear/>
<add name="Default" connectionString="Data Source=localhost;Initial Catalog=Sample01;Integrated Security=True;" />
</connectionStrings>
<appSettings>
<add key="contactEmail" value="[email protected]"/>
<add key="siteUrl" value="http://demo.example.com"/>
</appSettings>
</configuration>
Obsah XML dokumentu z definicí transformací Transforms.xml (uloženého v resource):
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<clear/>
<add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;" xdt:Locator="Match(name)" xdt:Transform="Replace"/>
</connectionStrings>
<appSettings>
<add key="contactEmail" value="[email protected]" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
<add key="siteUrl" value="http://example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
</appSettings>
</configuration>
Výpis průběhu prováděné transformace:
Transforming Source File: {System.IO.UnmanagedMemoryStream: TransformTest.Properties.Source.xml}
Applying Transform File: {System.IO.UnmanagedMemoryStream: TransformTest.Properties.Transforms.xml}
Executing Replace (transform line 4, 147)
on /configuration/connectionStrings/add[@name='Default']
Applying to 'add' element (source line 4, 6)
Replaced 'add' element
Done executing Replace
Executing Replace (transform line 7, 82)
on /configuration/appSettings/add[@key='contactEmail']
Applying to 'add' element (source line 7, 6)
Replaced 'add' element
Done executing Replace
Executing Replace (transform line 8, 76)
on /configuration/appSettings/add[@key='siteUrl']
Applying to 'add' element (source line 8, 6)
Replaced 'add' element
Done executing Replace
Output File: {System.IO.MemoryStream}
Transformation succeeded
Výsledný XML dokument (v příkladu vypsán na standardní výstup):
<configuration>
<connectionStrings>
<clear/>
<add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;"/>
</connectionStrings>
<appSettings>
<add key="contactEmail" value="[email protected]"/>
<add key="siteUrl" value="http://example.com"/>
</appSettings>
</configuration>
Vlastní transformace se provádí voláním metody Execute() objektu TransformXml. Vstupy (zdroj a definice transformací) a výstup (výsledný dokument) se předávají buď přímo jako jména souborů na disku nebo jako streamy, a nebo jako TextReader resp. TextWriter. Stream nebo reader/writer lze pro účely logování opatřit názvem. U výstupu lze dále explicitně specifikovat, že se případně po provedení transformace nemá provést jeho uzavření.
Součástí knihovny je dále také připravená třída AnonymousLogger obecné ”anonymní” implementace loggeru implementující interface ILogger. Logování se zapíná nastavením vlastnosti Logger objektu TransformXml.
Pokud je definice transformace chybná je, a to i v případě, že logování není zapnuté, log prováděných akcí stejně zachycen a vrácen jako součást vyhozené výjimky XmlTransformationFailedException. Případně můžeme při volání metody Execute() nastavit parametr throwOnError na hodnotu false, pak budeme o případném selhání transformace informováni pouze bool návratovou hodnotou. To může mít uplatnění v kombinaci právě s prováděním vlastního logování.
Zdrojové soubory knihovny Microsoft.Xml.Transform jsou ke stažení zde.