Handling Multiple Environment Configurations with .NET 2.0
Dealing with connection strings and configuration information is something developers do every day. The problem is compounded when you have to worry about local database connections, QA/Test deployments, and finally your release into production. Luckily with .NET 2.0 configuration is easier and the ability to externalize configurations combined with a little craftiness on your part will make builds easier to deal with.
Let's say you have 4 environments to deal with. Your local machine, an automated build server, a test deployment for QA to do user acceptance testing, and the production release. Each one of them has different needs and will access information like database connections in a variety of places. By default when you create a new project (any project) in Visual Studio you get two configurations, Debug and Release. Debug has symbolic debugging turned on, Release doesn't (plus there are some other optimizations but we don't care about those right now).
In 2.0 application configuration is easier with a little something like this:
142 <connectionStrings configSource="ConnectionStrings.config"/>
Rather than having our connection string all spelled out in App.Config or Web.Config, we can redirect it to a completely separate file. As the connection strings may change from environment to environment you don't want to have to edit this file manually, especially if it's being checked into source control so you're always changing it. This means the next time your buddy pulls down the file he'll have whatever change you made (maybe after a deployment to Test) and have to change it back. Worse yet is you might forget to edit this during a deployment (there's no way to force you to) so the next this is you deploy your Smart Client app with a connection to "localhost". Not cool.
So here's a strategy that might work for you. Create a config file for each environment you have then during a pre or post build step (doesn't matter which) in your main forms build you can copy the appropriate file as needed. This way your App.Config or Web.Config file doesn't change but the right settings are picked up from the file(s) you need.
First create the configurations you need. This is done by right clicking on the Solution and selecting Configuration Manager. Then in the "Active solution configuration" drop down, select "<New...>" to create them. You can accept using the default Debug and Release configurations if you want, but I find that "Debug" isn't very informative as I might want a debug version both on my local machine and my automated test or shared dev server. Here's a configuration I use that might suggest something that works for you:
- localhost - Based on Debug but indicates to me I'm building and running locally
- AutomatedBuild - Based on Debug or Release (your choice) but used for the automated build server. For example you may not want the automated build to deploy any web or report projects so it's good to have a new configuration here.
- Test - Based on Debug and used for your testers. You might want to base this on the Release configuration (depending on how savvy your testers are) and you might want to name this something more appropriate to your environment (like QA or something)
- Production - Based on Release and meant to be the build you deploy to your customer
Note these are all new configurations rather than renamed ones. Why? If you simply rename a configuration that's done at the Solution level. The project configuration names in each project will still be Debug and Release so it's confusing when you're setting up what configuration of a project is matched against the build. Trust me, just create new configurations and you'll be fine.
Now that you have your configurations setup, create a new file for each configuration. This will hold the connection strings for each configuration. Name them using the following convention:
ConfigurationName.ConnectionStrings.config
For example if you had localhost, Test, and Production as your configurations you would have the following new files in your main project:
- localhost.ConnectionStrings.config
- Test.ConnectionStrings.config
- Production.ConnectionStrings.config
The contents of each of these files is dependent on the environment but will look like this:
1 <connectionStrings>
2 <add name="LocalSqlServer"
3 connectionString="Data Source=localhost;Initial Catalog=DatabaseName;User Id=sa;Password=password;"
4 providerName="System.Data.SqlClient" />
5 </connectionStrings>
Finally to make it all work, go into your main project and setup a pre or post build event that looks like this:
copy "$(ProjectDir)$(ConfigurationName).ConnectionStrings.config" "$(ProjectDir)$(OutDir)ConnectionStrings.config" /Y
What does this do? It grabs the configuration file for the appropriate build and copies it to your output directory with a common name. That name is what's referenced in your App.Config or Web.Config as shown above.
Now when you build your system, depending on each configuration, it will copy the appropriate file with the correct settings but nobody needs to edit the main configuration file as it always references a common name (ConnectionStrings.config). Cool huh?
Note you can use this for a variety of sections in config (there are restrictions). Check the API documentation on what you can and can't externalize but connection strings and app settings are one of them which is good enough for 90% of the universe.
Of course, after all this is said and done there is another way to handle your environment issues. Just use NAnt or MSBuild, but then that's a whole 'nuther blog post isn't it?
Enjoy!