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:
    • Include path directive to include external files into the input file being processed.
     
    <?#include "/path/to/file.txt" ?>
    • Xml Include path directive to include external files inside an input file formed as xml.
     
    <flexibleconfig path="/path/to/file.txt" />
    • Include path must allow for parameter replacement at run time.
     
    <flexibleconfig path="$(MSBuildStartupDirectory)\bin\MailSettings.config" />
    • Support for inline C# code - this will be evaluated by the interpreter
     
    <?="string value" ?> 	

     
    <?=((IsCondition)? "option1":"option2") ?> 	
    • Support for large C# code blocks - this will be evaluated by interpreter
     
    	<?flexibleconfig  
    		// csharp code here
    		if(IsCondition)
    		{
    	?>
    		option1
    	<?flexibleconfig  
    		}
    		else
    		{
    	?>
    		option2
    	<?flexibleconfig  
    		}
    	?> 	
    • Allow from C# to write to standard output when being interpreted
     
    <?flexibleconfig 
    if( DevLocation == "PRODUCTION" )
    { ?> 
        <?=System.IO.File.ReadAllText(LocationConfigurationPath + @"\connectionStrings.config") ?> 
    <?flexibleconfig 
        writer.AppendLine(" write to stream while executing c#");
    } 
    else 
    { 
        string server = String.Empty; 
        string catalog = String.Empty; 
        switch(ApplicationId) 
        { 
            case "robusthaven.com.www": 
                 catalog="robusthaven"; 
            break; 
        } 
    
        switch(DevId) 
        { 
           case "LEBLANCMENESES": 
                 server = @"."; 
           break; 
           default: 
                  throw new ArgumentException("DevId not defined when establishing a connection string."); 
           break; 
        } 
    ?> 
    
        <add name="sqlConnection" connectionString="Persist Security Info=False;Integrated Security=true;Initial Catalog=<?=catalog ?>;server=<?=server ?>;pooling=false;MultipleActiveResultSets=true"/> 
    
    <?flexibleconfig 
    } 
    ?>
    • All other text that is not any of the above will be pushed to standard output, and remain unprocessed.
  2. As the input file is being processed, the interpreter will push the output stream to the OutputFile.
  3. If the input cannot be processed an error must be written to the ErrorLogFile.
  4. 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:

  • Parameters: (input) is a dictionary that can be used inside the BaseLineFile.
  • BaseLineFile: (input) is the input file that is conditionally evaluated for each user/platform.
  • ErrorLogFile: (output) is the output location of the error log file in case the task fails.
  • OutputFile: (output) is the output location of the file that needs to be regenerated.

 

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" />

Tags: MSBuild, Change Management, Configuration, Release Management,

 

Comments


No comments

X

Leave a Response

loading Processing.. Please wait..

All comments on this article are moderated

(will show your gravatar)