TheGeekery

The Usual Tech Ramblings

Visual Studio Projects, and Multiple Environments

I’ve recently been put in control of managing the source control, and deployments in the office. It’s an interesting project, and I’m looking back over some of the stuff we’ve done to get our mass of code to work, build, and roll out to 4 different environments. However, I wanted something more manageable, and flexible, that we could use it through our entire product line, instead of having to customize it for each project/product.

The Many Ways Of The Config

There are plenty of ways to handle multiple environments.

Multiple Environment Configs

The most basic is currently how we handle it… Each environment gets its own configuration, and when it comes to deployment time, we rename the configuration files (see the image to the right). This is okay to a point, and is okay when you’re working with small configuration files that don’t experience much change, as you’ll need to maintain duplicate configurations.

Variable Environment Configs

This is a little more complicated, and often involves a little bit of code tweaking. You maintain a single configuration, which contains variables for each environment, and a single variable (usually) to control access to each code. Something like this:

<appsettings>
  <add key="environment" value="dev" />
</appsettings>
<connectionstrings>
  <add name="dev_dbconn" connectionString="server=1.2.3.4;user id=myuser;password=mypass;databases=mydatabase;" />
  <add name="qa_dbconn" connectionString="server=4.3.2.1;user id=myuser;password=mypass;databases=mydatabase;" />
  <add name="prod_dbconn" connectionString="server=2.1.3.4;user id=myuser;password=mypass;databases=mydatabase;" />
</connectionstrings>

This is relatively easy to maintain, however, your configs can get pretty big, and sometimes makes reading a little complicated.

Variable Replacement Configs

This one requires the use of some external tool to do some variable replacements. It’s pretty obvious on this one…

  <add name="db_conn" connectionString="${dbname}" />

Then another file containing a configuration file which details the replacements. For example:

[dev]
dbname=sever=1.2.3.4;user id=myuser;password=mypass;databases=mydatabase;

A third party app then uses the configuration file to update your main configs.

Substituting Configs

This is one I’m playing with at the moment. Similar to the variable replacement, just handled differently. I discovered this one whilst searching around, and found an interesting feature call XmlMassUpdate, which is part of the MSBuild Tasks project. The idea is you start off with your base configuration, and then create a substitutions configuration. Doron Yaacoby has a great sample on setting this up.

I had a little issue at first with this. I added the information to the project file, told it to build, and saw no different. So I tried adding some “debugging” to my additions.

<target Name="MyAfterBuild">
  <message Text="I'm running" />
   <xmlmassupdate ContentFile="$(OutputPath)web.config" 
       SubstitutionsFile="$(SubstitutionsFilePath)" 
       ContentRoot="/configuration"
       SubstitutionsRoot="/configuration/substitutions/$(Configuration)"/>
</target>
``` xml

Notice I added a `Message` task to the target.  This should output "I'm running" on the output logs, but no such luck.  I skimmed around some more, but didn't really find the solution.

I read the build project some more, and found the reason.  Build projects allow you to import other projects to build some inheritance.  I had been defining my build targets at the very top of the file, and they were being wiped out by a later import.  So I moved it further down[^1], and presto... My new builds were working, and had a semi-successful build.

The next issue was permissions.  The file it attempts to do the replacement in is the web.config file in the main directory.  The problem with this was the file it was trying to update was stored in TFS, and as such, marked as read-only until checked out.

After looking through some of the imports, I found another useful variable, WebProjectOutputDir.  Changing the XmlMassUpdate to use that variable instead.

``` xml
<xmlmassupdate ContentFile="$(WebProjectOutputDir)\web.config"
  SubstitutionsFile="$(SubstitutionsFilePath)"
  ContentRoot="/configuration"
  SubstitutionsRoot="/configuration/substitutions/$(Configuration)" />

When combined with command line arguments, you can push the output to another directory, and the config file is magically updated.

Comments