MSBuild FlexibleConfigTask

A few months ago, we concentrated on a technical debt feature, so that we could improve our team’s development work flow. Our work became an MSBuild task & FlexibleConfig.FlexibleConfigTask, and this solved a lot of issues that used to bother developers.

This feature became high priority as a result. If you are suffering from similar problems, then you are welcome to purchase this feature though our products page.

Backlog

As a build manager, I need a task that allows me to write inline C#, to conditionally evaluate sections of a file. This will allow me to easily centralize changes in one file with conditional logic for various environments (Integration, QA, and Production Like Environment) that I need to support.

As a release manager, I want to automate replacing development configurations with production values, and to have them integrated with the existing file that the build managers and developers are maintaining.

As a developer, I want a task that can switch: > > > - connection strings, appSetting values in Web.config and App.config > - Silverlight endpoint locations in ServiceReferences.ClientConfig > - configuration/system.serviceModel/services section of Web.config between mock versions of the WCF service and the concrete implementation. > - customErrors mode properties in Web.config > - forms timeout properties in Web.config > - requireSSL properties in Web.config > - WCF behaviors includeExceptionDetailInFaults and httpGetEnabled in Web.config > - specific settings specific to a developer, without affecting integrating environments > - a flexible Web.config and App.config, and that does not block a distributed workforce not sharing a common infrastructure during development > > > > so that I can make the configuration highly flexible. This would allow me to run the project using mocks, without the specific infrastructure required to run the project.

Feature Prioritized

Having groomed the backlog, we noticed that these user stories have a common theme. So we grouped them into a feature called “FlexibleConfig Task”. We hand picked a couple of users, and requested them to add some technical specifications to their story. Here are the specifications that were created:

  1. The input, BaseLineFile, to be processed, has to be broken into an abstract syntax tree. The grammar should allow for the following:
  1. Support for large C# code blocks - this will be evaluated by interpreter

                   <?flexibleconfig
       // csharp code here
       if(IsCondition)
       {
     ?>
       option1
     <?flexibleconfig
       }
       else
       {
     ?>
       option2
     <?flexibleconfig
       }
     ?>
    
                
  1. As the input file is being processed, the interpreter will push the output stream to the OutputFile. 4. If the input cannot be processed an error must be written to the ErrorLogFile. 5. We must be able to pass Parameters into the file being processed. This has to have a collection of key value pairs that will be expanded when evaluating include paths and C# statements.

Introducing our MSBuild FlexibleConfig.FlexibleConfigTask

Our FlexibleConfigTask was unit tested to solve all the requirements described above. Our integration into an MSBuild script looks as follows.

          <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
	<RobustHavenTasksPath>$(MSBuildStartupDirectory)\Externs\msbuild\RobustHaven.Tasks</RobustHavenTasksPath>
</PropertyGroup>
<Import Project="$(RobustHavenTasksPath)\RobustHaven.Tasks.Targets"/>

<Target Name="SyncConfiguration">

	<ItemGroup>
		<FlexibleConfigParameters Include="MSBuildStartupDirectory">
		  <Key>MSBuildStartupDirectory</Key>
		  <Value>$(MyMSBuildFolder)</Value>
		</FlexibleConfigParameters>
		<FlexibleConfigParameters Include="RobustHavenDevId">
		  <Key>RobustHavenDevId</Key>
		  <Value>$(RobustHavenDevId)</Value>
		</FlexibleConfigParameters>
		<FlexibleConfigParameters Include="RobustHavenDevLocation">
		  <Key>RobustHavenDevLocation</Key>
		  <Value>$(RobustHavenDevLocation)</Value>
		</FlexibleConfigParameters>
		<FlexibleConfigParameters Include="RobustHavenDevTask">
		  <Key>RobustHavenDevTask</Key>
		  <Value>$(RobustHavenDevTask)</Value>
		</FlexibleConfigParameters>
		<FlexibleConfigParameters Include="ApplicationId">
		  <Key>ApplicationId</Key>
		  <Value>$(ApplicationId)</Value>
		</FlexibleConfigParameters>
	</ItemGroup>


	<Message Text="Creating Www.Web.config" />
	<FlexibleConfig.FlexibleConfigTask
		Parameters="@(FlexibleConfigParameters)"
		BaseLineFile="$(MyMSBuildFolder)\Www.Web.config"
		ErrorLogFile="$(MSBuildStartupDirectory)\Error.log"
		OutputFile="$(TemporaryFolder)\Www.Web.config"
	/>
</Target>
</Project>

        

Line 34, contains our custom MSBuild task that takes two inputs and provides two output:

Sample Usage:

We have integrated our build tasks with every project that we are working on. The following are incomplete excerpts that show common examples where you can use this task.

  1. Resolving appSettings for various environments:

                   <add key="rfidhostname" value="<?= ((DevLocation == "QA")? "xtest2008":"dev03") ?>" />
    
                
  2. Resolving ‘Production’ values when pushing to production:

                   if(DevLocation=="PRODUCTION")
     {
         writer.AppendLine(System.IO.File.ReadAllText(LocationConfigurationPath + @"\mailSettings.config"));
    
     }
    
                
  3. Supporting developer specific settings during development.

                   <?flexibleconfig
     string dbInstance = string.Empty;
    
     string dbCompany = string.Empty;
    
     string prefix = string.Empty;
    
     dbInstance = ProductName + DevEnvironment;
    
     if( DevEnvironment == "PRODUCTION")
     {
       if(DevEnvironment == "PRODUCTION")
       dbInstance = "ProductNameAPP";
    
     dbCompany = "Company";
     }
     else if( DevEnvironment == "DEV" || DevEnvironment == "TEST" || DevEnvironment == "UAT" )
     {
     if(DevEnvironment == "UAT")
       dbInstance = "ProductNameDB";
    
     if(DevBranch == "DEMO" || DevBranch == "TRAINING" || DevBranch == "MODELOFFICE")
       dbInstance = "ProductNameUAT";
    
       prefix = ProductName + DevEnvironment + DevBranch;
       dbCompany = prefix + "Company";
     }
     else
     {
       prefix = DevBranch != "TRUNK-WEBAPP" ? ProductName + DevEnvironment + DevBranch:"";
       dbCompany = prefix + "Company";
    
     switch(DevId)
     {
       case "LEBLANCMENESES":
         if(DevEnvironment == "HOME.LAPTOP")
         {
           dbInstance = @"JANIELAPTOP\MSSQLSERVER1";
         }
         else if(DevEnvironment == "HOME.DESKTOP")
         {
           dbInstance = @".";
         }
         else if(DevEnvironment == "HOME.VM")
         {
           dbInstance = @".";
         }
       break;
    
       case "EFLORES":
       case "DENIS":
             dbInstance = @".";
           break;
       default:
         throw new ArgumentException("DevId not defined when establishing a connection string.");
     }
     }
     ?>
    
                
  4. Supporting staging environments and local development settings in ServiceReferences.xslt. VirtualPath is considered by BaseUrl.

                   <endpoint address="<?=BaseUrl ?>/Services/Security/SharedRoleService.svc"
       binding="customBinding" bindingConfiguration="CustomBinding_ISharedRoleService"
       contract="SharedRoleServiceProxy.ISharedRoleService" name="ISharedRoleService"/>
    
     <endpoint address="<?=BaseUrl ?>/Services/Security/PermissionService.svc"
       binding="customBinding" bindingConfiguration="CustomBinding_IPermissionService"
       contract="PermissionServiceProxy.IPermissionService" name="IPermissionService" />