Saturday, March 5, 2011

Customize your .config for each developer.

Recently (VS2010) Microsoft introduced a technique for customising .config files (app.config, web.config) based on a target build / publish. These config transforms are used to update a config file, as it is being built for a particular configuration. I quite like the transform language, it feel straight forward; simple use locators to identify target elements, and then actions to replace elements or attributes as desired.
Over the last couple of months I have been using these transforms at work, but in my situation they are not 'the whole answer'.
I have just introduced WS-Federated security for passive login and delegation (with WIF). As anyone who has done this knowns.. there are A LOT of config settings all of which need to be correct, additionally if your going to set this up so that each developer can work independently, config settings such as Certs / Audience uri's need to be per-developer machine. Initially I was tempted to use config transforms for this problem, but alas transforms are build-config centric; this approach would mean a custom build for each developer [machine]... >10 developers.. no go.

The alternate; MSBuild with some extensions, and a per-developer build file.

1. Used the MSBuild Extension Pack, it has a nice Detokenize task.

2. Create a master config file: web.master.config use this (from now on).
Replace parts of the config with tokens, by default use $(tokenName) format.
I like to have a comment section at the beginning of the config file, that reports on the particular settings.
<!--
Generated from app.master.xml
Using:
UserName = $(UserName)
LocalPath = $(LocalPath)
-->

3. Create a local.targets project file with a BeforeBuild target.
The project defines a default target BeforeBuild which is called as part of the normal visual studio project build.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="BeforeBuild">
    <!--MSBuild extensions, for the DEtokenize task.-->
    <Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>

    <Target Name="BeforeBuild">
        <ItemGroup>
            <MasterFile Include="app.master.config" />
            <ConfigFile Include="app.config" />
        </ItemGroup>
        <ItemGroup>
            <Token Include="UserName">
                <Replacement>Developer</Replacement>
            </Token>
            <Token Include="LocalPath">
                <Replacement>D:\Source\Tmp\Sample</Replacement>
            </Token>
        </ItemGroup>
        
        <Copy SourceFiles="@(MasterFile)" DestinationFiles="@(ConfigFile)" OverwriteReadOnlyFiles="true" />
        <MSBuild.ExtensionPack.FileSystem.Detokenise 
            TaskAction="Detokenise" 
            TargetFiles="@(ConfigFile)" ReplacementValues="@(Token)"/>
    </Target>
</Project>

The project itself consist of not much more than a Copy task to copy master.config file(s) over the config files. Followed by a Detokenise task to change the config file.

4. (xml) edit the project to import the per-developer project.
Simply add the following toward the end of the project (after the other import entries).
<Import Project="local.targets" Condition="Exists('local.targets')"/>

5. (Re)Open the project, and next build the developer defined settings will be used.

Quick sample application: http://dl.dropbox.com/u/8153235/SampleMasterConfig.zip

too easy.

A short list of C# coding issues.

Injecting services into entities It been bugging me for a while now that to use services in a entity can be a bit of pain. It usually starts...