Sunday, April 5, 2009

Environment-Specific ServiceReferences.ClientConfig for Silverlight

Pretty much every project I work on requires the ability to have environment-specific configuration settings: local, development, testing, and production. It's the last thing I ever want to think about when I'm just trying to get my first iteration of a new project working--and I always regret not building it in from the beginning (especially when it comes time to put the project into Continuous Integration).

Things are no different working with Silverlight. If your Silverlight application makes any service calls, there is a good chance you need to call different Uri's in each environment. The configuration for these services is stored in a file called ServiceReferences.ClientConfig.

Additionally, the web site that hosts your Silverlight application may have the same type of environment-specific requirements. Wouldn't it be nice to have one way of solving this problem?

There are plenty of techniques for accomplishing this type of task, especially for app.config in WinForms and web.config in ASP.NET projects. The usual suspects are:

  • Create a NAnt/MSBuild script that replaces the config file at build-time with the proper one for the environment being built
  • Create an MSI that contains all versions of the config file and deploys the one for the environment from a user-selected chosen by the user (or via command-line in quiet mode
  • Create multiple Solution Configurations for each environment, and replace the config at build-time using a pre-build command.

I've used all three techniques over the years. It turns out that the first two don't make much sense for Silverlight, as the ServiceReferences.ClientConfig is bundled into the XAP file that is created at build time. Sure, you can create some type of post-build step that replaces the ClientConfig file in the XAP, but that could get interesting to manage. You don't have an MSI for a XAP, so that option is out as well.

I heavily favor option 3--especially for Silverlight development. The best description I have seen for accomplishing option 3 is good ol' Scott Hanselman's blog entry on Managing Multiple Configuration Environments with Pre-Build Events. Right at the beginning, he says:

"Here's the general idea. It's not too hard. I'll use an ASP.NET Web Site and web.config as an example, but this will work with most any kind of project, exe's or .dll's."

He's right. It works great for Silverlight projects as well! No ripping apart/adding to XAP files after they're built. No NAnt or MSBuild script required. No manual changing of urls when it comes time to deploy. Just follow his post with the following differences:

  1. Make environment-specific ServiceReferences.ClientConfig files (instead of web.config).
  2. Use a slightly different Pre-Build Event that updates ServiceReferences.ClientConfig instead of web.config
  3. Optionally, update the copyifnewer.bat file to use xcopy /R /Y if you use TFS as your source control (thank you, TFS, for making files read-only...argh.)

I will only describe the differences below.

Make the environment-specific ClientConfig files

Just copy the ServiceReference.ClientConfig file that is built for you:


  
    
      
        
          
        
      
    
    
      
    
  

    

and paste the original file into configuration-specific files, such as ServiceReferences.ClientConfig.dev, ServiceReferences.ClientConfig.testing, and ServicesReferences.ClientConfig.production (and don't forget the .debug and .release versions to cover your default solution configurations).

Here's an example of ServiceReferences.ClientConfig.Testing (notice how the highlighted areas are different between the two files; this is the typical place where all of the ClientConfigs differ).


  
    
      
        
          
        
      
    
    
      
    
  

    

Create the Pre-Build Event to Copy the ClientConfig

Instead of replacing the web.config file, change the Pre-Build Event to replace the ServiceReferences.ClientConfig (only when it is different from what's already there):

"$(ProjectDir)copyifnewer.bat" "$(ProjectDir)ServiceReferences.ClientConfig.$(ConfigurationName)" "$(ProjectDir)ServiceReferences.ClientConfig"
        

Add the copyifnewer.bat File

And don't forget to add the copyifnewer.bat file to the root of your project (see Scott's post, lined above) for details on that. Remember, if you use TFS (or some other source control that insists on marking checked in files as Read-Only, you will need to update the .bat file to overwrite without prompting even if the file is Read-Only (xcopy /R /Y worked like a champ).

The Result

Here's a shot of the project structure, once these files are all added:

I love this solution as it works locally, within the IDE as well as on a build server using MSBuild, NAnt calling MSBuild, or NAnt calling devenv.exe directly!