Working with xml transformations in SharePoint: Part 1

A while back I made a post about how you can benefit from xml file transformations with other files that just web.config files (which is supported OOB in VS2010).
Since then I have made some adjustments to the transformation process and started to use it myself in some SharePoint projects so I though I´d make a two part series on how to I use this feature in my SharePoint project and elaborate so more on how this process actually works.

The sample application and the source code can be found in my github repo.

This first part will again go through the steps necessary to make a transformation work and in the second part I will show you how to make use of an app.config file (some settings really) in a SharePoint solution.

So, step 1…

Create a targets file

Start by creating a new file with the extension .targets (I´ll call mine ConfigurationTransformation.targets).

Copy & Paste this code into that file (we´ll go through the contents of that file in a minute):

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 <UsingTask TaskName="TransformXml"
 AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

 <ItemDefinitionGroup>
 <None>
 <TransformedFileDestination>CONFIG</TransformedFileDestination>
 <TransformOnBuild>false</TransformOnBuild>
 </None>
 </ItemDefinitionGroup>

 <PropertyGroup>
 <TransformConfigurationFilesDependsOn>
 DiscoverFilesToTransform;
 </TransformConfigurationFilesDependsOn>
 </PropertyGroup>

 <PropertyGroup>
 <CopyTransformedConfigurationFilesDependsOn>
 TransformConfigurationFiles;
 </CopyTransformedConfigurationFilesDependsOn>
 </PropertyGroup>

 <Target Name="TransformConfigurationFiles"
 DependsOnTargets="$(TransformConfigurationFilesDependsOn)">

 <TransformXml Source="@(FilesToTransForm->'%(FullPath)')"
 Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
 Destination="@(FilesToTransForm->'$(ProjectDir)%(TransformedFileDestination)\%(FileName).tmp%(Extension)')"
 Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />

 </Target>

 <Target Name="CopyTransformedConfigurationFiles"
 DependsOnTargets="$(CopyTransformedConfigurationFilesDependsOn)"
 AfterTargets="Build">
 <Copy SourceFiles="@(FilesToTransForm->'$(ProjectDir)%(TransformedFileDestination)\%(FileName).tmp%(Extension)')"
 DestinationFiles="@(FilesToTransForm->'$(ProjectDir)%(TransformedFileDestination)\%(FileName)%(Extension)')"
 OverwriteReadOnlyFiles="true"
 Condition=" Exists('%(RootDir)%(Directory)%(Filename)%(Extension)') " />

 <Delete Files="@(FilesToTransForm->'$(ProjectDir)%(TransformedFileDestination)\%(FileName).tmp%(Extension)')"
 Condition=" Exists('%(RootDir)%(Directory)%(Filename)%(Extension)') "
 ContinueOnError="true" />

 </Target>

 <Target Name="DiscoverFilesToTransform">

 <ItemGroup>
 <FilesToTransForm Include="@(None)"
 Condition=" '%(TransformOnBuild)' == 'true' " />
 </ItemGroup>
 </Target>

</Project>

In this example I´ll use two different approaches for working with app.config files.
I´ll start with the first one (makes sense hah?)

Option 1 – create a mapped folder

The first option/example will use a mapped folder {CONFIG/MyExample} as a destination for the transformed app.config file, so I´ll create a mapped folder to {CONFIG} with MyExample as a subfolder. In that folder create a file called app1.config (the file doesn´t need to have any content, it will be replaced, it is just used for VS to know that you wish to add a file to that location when you deploy the package).

NOTE: The name of the file is actually important since we will use a file with the same name on the root of the project and the transformation process expects them to have the same name.

Option 2 – a feature with an SPI

This example/option instead uses a feature to push the app.config file into a feature in {SharePointRoot/TEMPLATE/FEATURES/MyExample} so I have first added a folder {ConfigurationFiles} and in that folder an empty SPI (SharePoint Project Item) with the app2.config file inside it.
The file needs no content, we are going to replace the file with a transformed file.

The file though, need to have the deployment type set to elementfile:

When you do this, you can see in your feature that the element (SPI) included in the feature now has some files associated with it. (If you forget this, the app2.config file won´t be included in the feature)

The project structure now looks like this:

Now add the configuration files

Since I will use two approaches in this example, I have added two app.config files which I will call app1.config and app2.config just for the sake of differencing between them.

You can also see that I have also added two files with the name TEST in them. This refers to a configuration I have setup through the configuration manager called TEST:

This means that if I build my project under that configuration (TEST) the app1.TEST.config and app2.TEST.config files will be used as the transformation instruction. This we will add next…

In the app1.config and app2.config (without the name TEST) I have added one key called MyExample. I a real world scenario the files may contain two different sets of setting, but I just want to show you that we can use multiple .config files in a SharePoint project.

Next, I will go into the app.TEST.config files and add a transformation. I will, in my example, transform (or replace) the entire appSettings section but you have numerous options to choose from.

On the configuration node I have added a namespace (dt) which will later give me the ability to transform/replace the entire appSettings section.

Now it´s time to straighten up the structure of the project (make it look pretty). First, I would like my .TEST files to appear underneath the “regular” .config files. Going in to edit mode on the project file fixes that for me:

The DependUpon metadata I have added to the .TEST files tells VS to show one item underneath the other, like so:

So, I basically tell VS that app1.TEST.config depends upon app1.config and so on…like a code behind/designer file in asp.net

Finally, it is time to add the actual transformation

But wait, I before doing that let´s again have a look at the .targets file in sections to see what will happen once we tell MSBuild to use our new target/instruction:

First, we import the “TransformXml” target from the Microsoft.Web.Publishing.Tasks.dll so we can use that task to transform the app.config files.

Next we define an ItemDefinitionGroup and add some new metadata to our None files, TransformOnBuild (default to FALSE) and TransformFileDestination (default to CONFIG).

TransformOnBuild is used to tell our target that we want to transform that file and TransformFileDestination is a project relative path to where our transformed file will end up.

By default the web.config transformations that work on web application projects (WAP) will put the transformed file in the BIN directory. Unfortunately that won´t work for our SharePoint project since we will need some sort of packaging instruction (manifest.xml) to put that file into a WSP, hence the project relative path and the two options (mapped folder or SPI with feature).

Next are two property groups which tell our targets that a target called CopyTransformedConfigurationFile will depend upon another target called TransformConfigurationFiles. That target in turn depends on the DiscoverFilesToTransform target. A little workflow you might say.

Next, let´s look at the copy target and the discover files target.

It´s a lot of fancy xml but let´s just leave it with that discover files will find all None files with metadata TransformOnBuild set to true.

The copy target then uses those files and puts them into the project relative (projectdir) path that we have specified. You might see that the source for the copy command uses a file with the name tmp in it. I had to do it like that to avoid locks. The transformation creates a file with name tmp in it in the project relative path and then the copy target copies that tmp file to the actual file and finally deletes the tmp file. A little hack, but it works.

One thing to notice about the copy target is that it runs after the Build target but since it in turn depends on the transform target that will run first. But, that target will need the discover target to run beforehand so discover will run first of all.

Discover -> Transform -> Copy, that´s the workflow.

And finally the transform target will transform the None files with TransformOnBuild set to true. It will use a file with same name but with the name of the configuration (TEST in my example) to perform the transformation. The output will be called {filename.tmp.config}

OK, the final step is to include our two .config files in the process and then tell MSBuild to use our targets file. Edit the projects file again and add this:

As you can see I have included my two options in the transformation process but with different paths. I´m telling MSBuild to use transformations on app1.config and app2.config

The final thing is to import our targets file. Since I placed it in the project itself the path to it is relative to the project. If you place it in some other place, like in the solution folder the path would be something else (like $(SolutionDir) ).

OK, let´s try it out. I will now build my project with the TEST configuration.

If I now go and look in either the app1 or app2 config files under the SPI or the managed folder I created I can see that my values from the TEST configuration have been merged into that file.

Great!

In the next post I will show you how to make use of those files when using settings inside SharePoint

References:

http://blogs.msdn.com/b/webdevtools/archive/2010/11/17/xdt-web-config-transforms-in-non-web-projects.aspx

, , , ,

  1. #1 by spartan422 on October 19, 2012 - 21:59

    I am trying to implment this but I am getting an “Unable to Copy file ….. The process cannot access the file …. because it is being used by another process” Any ideas on how to get past this?

    • #2 by Johan Leino on October 23, 2012 - 19:40

      something is locking the file you’re trying to write to probably…

  1. Working with xml transformations in SharePoint: Part 1
  2. Working with xml transformations in SharePoint: Part 2 « Johan Leino
  3. Making use of configuration file transformations in SharePoint 2010 « Johan Leino
  4. Team Build 2010: XML Transforms on Team Build 2010 for Non-Web Application Projects | Microsoft Enterprise Technologies

Leave a comment