Each of these environments will typically (at least hopefully) have its own version of the database (usually on a different server, especially for production), and other settings that logically need to vary from one environment to another (the most obvious being for instance whether or not "debug" is enabled). There may additionally be different mail settings, different web service endpoint settings and so on.
Managing these differences has been tedious and error-prone to do manually, and not exactly a piece of cake with automated systems either (I remember having to include post-build steps to copy and rename configuration files, for instance).
Fortunately, Visual Studio 2010 includes a feature which improves this situation considerably.
If you create a new project using one of the Web templates (such as ASP.NET Web Application or ASP.NET MVC 3 Application for example, but not Web Site) and glance at the Solution Explorer you'll notice an arrow next to the Web.config, indicating the presence of dependent files. If you expand this arrow you'll see the following:
If you now open Web.Release.config, you'll see something like this:
Note the unfamiliar xdt:Transform attribute on the element - this has the value "RemoveAttributes(debug)", and it does exactly what it says: it is an instruction to remove the debug attribute from the compilation element. Since we do not typically want out production code to run as debug, this makes sense for the release version of a config file.
By default, any values in the original Web.config file not explicitly removed, overridden or modified will carry through to the final output, so the only things you need to specify explicitly in the transformation files are those you want to add, remove or change.
If you look at the commented-out examples in the Web.Release.config file (and I suggest you do as a good place to start), you'll notice that the first example shows a section containing an entry for a "MyDB" connection string, and in addition to the xdt:Transform attribute we have already seen there is an xdt:Locator attribute, with the value "Match(name)", which as you can probably guess instructs that in selecting the element to modify, it should be matched according to the name attribute of the element.
A connectionStrings section may contain multiple connection strings, so to identify which one you want to apply a particular transformation to you use a Locator attribute (the same applies in other cases where there is potential ambiguity - note that you didn't need one on the element because there is only one.
The Web.config transform is applied when the application is published - one obvious way to try this out is to select "Build->Publish " from the Visual Studio menu, and then use the publish method of your choice - I find that publishing to the local file system is a good way to verify results quickly.
The Web.config transformation applied is the one corresponding to the currently selected build configuration - so for instance to apply the transformations specified in Web.Release.config you would publish with the build configuration set to Release. If you add additional build configurations (which if you use automated builds you probably will) you can generate additional configuration transform files, which will be named to reflect the new or additional build configurations.
If for instance you use the Configuration Manager to create a new build configuration named "Test", you will find that when you right-click Web.config the Add Config Transforms context menu entry is now enabled. If you click this option it will generate a Web.Test.config file (as well as config transform files for any other build configurations you may have created).
Note that if for example you chose to base your hypothetical Test build configuration on Release (which is what I generally do) it will generate a Web.Test.config file that is a duplicate of the default (unmodified) Web.Release.config file.
If we go back to our Web.Release.config file, remove the comments around the connectionStrings section and publish with build configuration set to Release, you may possibly be surprised by the result. The output Web.config file includes an ApplicationServices connection string because it is included in the project's Web.config file, but it does not include the MyDB entry that we added in Web.Release.config - this is because the xdt:Locator attribute is set to "Match(name)", and there is no existing connection string with a name attribute of "MyDB", so the match fails.
We can fix this by adding a "MyDB" connection string in the project's Web.config and leaving its connectionString attribute as an empty string. This is a good approach when an element such as a connection string is present in all build configurations but we want to give it different values in each (or for given subsets). If on the other hand you wanted to add a completely new connection string in one configuration (unlikely in the case of connection strings, but there are other elements where it would make sense) you would omit the xdt:Locator attribute and give xdt:Transform the value "Insert".
So what else can you do with xdt:Transform?
Possible values are as follows: Replace (replaces the matching element, or the first of a series if there is more than one match), Insert (inserts a new element at the end of the selected collection), InsertBefore (followed in brackets by an absolute XPath expression used to identify the element before which it is inserted), InsertAfter (similar to InsertBefore except for the position at which it inserts), Remove (removes the selected element or the first of a matching), RemoveAll (removes the selected element or elements), RemoveAttributes (removes the specified attribute or a comma-delimited list of attributes - this is the transform that is enabled by default for the element's debug attribute in Web.Release.config) and SetAttributes (allows you to change selected attributes to the values they have on element containing the SetAttributes attribute, without replacing the elements containing those attributes).
More on xdt:Locator
The examples of matching I've shown have been simple matches on the value of an attribute, but the Locator syntax is actually much more flexible than that. "Match" can take not just a single attribute but a comma-delimited list, and for more complex matches you can instead use "Condition" or "XPath": both take an XPath expression as their argument, but whereas with "Condition" the expression evaluates a path relative to the selected element, with "XPath" it is absolute and applies to the entire configuration.
MSDN documentation covering Transform and Locator syntax (with examples) can be found here.
Publishing with Automated Builds
While it's all very well being able to publish a web application from your PC and have transforms applied as part of that process, in the real world (at least the nice part of the real world, where we all wish we lived) the finished web application will be built from source control by an automated build process and then deployed, perhaps as the final step in that process.
Microsoft are very fond of people using deployment packages to deploy web applications, and their online documentation covers using web config transforms with deployment packages in some detail (this is a good place to start). If that's what you're using you should be fine (if there are any issues with the build automation, it's worth checking out this thread on stackoverflow.com).
However, if your build process simply builds the web application for XCopy deployment (which I'm certain is a very common scenario) you may be wondering how to cause the transformation to be applied, since in this case the build is not performing a "Publish" as such. Fortunately, you can handle this situation fairly easily by adding an AfterBuild step to your project file that executes TransformXml and then following the transform cleans up by deleting all every file from the output that matches the pattern "Web.*.config" (therefore leaving your output Web.config file and deleting the transform files, which you are unlikely to want to deploy to the web site). I encountered this issue myself and resolved it as described a little over a year ago, so you can find the solution described in some detail on my blog.
Download a trial of Visual Studio 2010.
About the author
Kevin Daly has been writing code professionally since 1986 and for kicks since about 1981. He has been using .NET and C# continuously since .NET 1.0 was still in beta (2001 For Those Who Came In Late). His programming interests span desktop, web and mobile development, but he is particularly interested in the XAML-based (or at least XAML-aware) technologies such as WPF, Silverlight and WinRT. He is also interested in creating legions of killer robots (although that one's been slow getting off the ground). Other examples of his views, rants and even the odd code sample can be found on his blog at http://kjdaly.com. He will now stop talking about himself in the third person because it's frankly a bit weird.
Other related posts:
Secure Development Tips and Techniques for use in Asp.net websites and web applications
Consuming JSON web APIs with Visual Studio