Wednesday, June 22, 2011

Unit Testing Event Assignments

A quick note on how to test for event assignments using NUnit and Rhino.Mocks for stub and mock scenarios.

Stub scenario:

  1. [Test]
  2. public static void AssignPageEventHandlers_SetsPageEventHandlers()
  3. {
  4.     var stubView = MockRepository.GenerateStub<INavigationPageBaseView>();
  5.     var obj = new TestNavigationControllerRoot(stubView);
  6.     obj.AssignPageEventHandlers();
  7.     stubView.AssertWasCalled(v => v.PreInit += Rhino.Mocks.Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PagePreInit")));
  8.     stubView.AssertWasCalled(v => v.Init += Rhino.Mocks.Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PageInit")));
  9.     stubView.AssertWasCalled(v => v.Load += Rhino.Mocks.Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PageLoad")));
  10. }

Mock scenario:

  1. [Test]
  2. public void PageLoadMasterController_ExpectedBehavior()
  3. {
  4.     var stubExProcessor = MockRepository.GenerateStub<IExceptionProcessor>();
  5.     stubExProcessor.Stub(e => e.GetCurrentUserErrorMessage()).Return("msg");
  6.  
  7.     var mockView = Mocks.StrictMock<IErrorView>();
  8.     using (Mocks.Ordered())
  9.     {
  10.         Expect.Call(() => mockView.PreInit += Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PagePreInit")));
  11.         Expect.Call(() => mockView.Init += Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PageInit")));
  12.         Expect.Call(() => mockView.Load += Arg<EventHandler>.Matches(eh => eh.Method.Name.Equals("PageLoad")));
  13.         Expect.Call(mockView.IssueMessageLiteralText = "msg");
  14.     }
  15.     Mocks.ReplayAll();
  16.     var obj = new ErrorController(mockView) { NavigationBuilderFactory = MockNavBuilderFactory, NavigationMasterController = MockNavigationMasterController };
  17.     obj.PageLoad(stubExProcessor);
  18.     Mocks.VerifyAll();
  19. }

Friday, March 25, 2011

Explicitly implement interface to assist with Unit Testing and Method Accessibility

I commonly encounter the following scenario when I am designing a class and unit tests:

  • I want to break down a public method into smaller methods so that I can unit test complex logic in more manageable chunks
  • I want to limit the accessibility of my smaller methods to private

A solution to this problem is to have my class explicitly implement an internal interface.  The internal interface contains signatures for all of the private smaller methods.

These code samples demonstrate my solution.  The SourceCode class implements the ISourceCode interface for consumption by other assemblies and also explicitly implements the ISourceCodeUnitTest interface for unit testing purposes.  Unit tests are able to isolate the logic in the ValidateString method.  Other unit tests isolate the logic in the MyPublicValidator method without having to re-test the ValidateString method because ValidateString can be stubbed.

Source Code:

  1. using System;
  2.  
  3. namespace Blog
  4. {
  5.     public interface ISourceCode
  6.     {
  7.         string MyPublicValidator(string input);
  8.     }
  9.  
  10.     internal interface ISourceCodeUnitTest
  11.     {
  12.         bool ValidateString(string input);
  13.         string MyPublicValidator(ISourceCodeUnitTest sourceCode, string input);
  14.     }
  15.  
  16.     public class SourceCode : ISourceCode, ISourceCodeUnitTest
  17.     {
  18.         public string MyPublicValidator(string input)
  19.         {
  20.             return ((ISourceCodeUnitTest)this).MyPublicValidator(this, input);
  21.         }
  22.  
  23.         string ISourceCodeUnitTest.MyPublicValidator(ISourceCodeUnitTest sourceCode, string input)
  24.         {
  25.             // complex logic in separate ValidateString method
  26.             bool isValid = sourceCode.ValidateString(input);
  27.  
  28.             if (isValid)
  29.                 return "The input is a valid string.";
  30.             else return "The input is not valid.";
  31.         }
  32.  
  33.         bool ISourceCodeUnitTest.ValidateString(string input)
  34.         {
  35.             // complex logic
  36.             return string.Compare(input, "valid string", true) != -1;
  37.         }
  38.     }
  39. }
Unit Tests:

  1. using System;
  2. using NUnit.Framework;
  3. using Rhino.Mocks;
  4.  
  5. namespace Blog.Tests.Unit
  6. {
  7.     public class SourceCodeTests
  8.     {
  9.         [Test]
  10.         public void MyPublicValidator_InvalidInput_ReturnsInvalidMessage()
  11.         {
  12.             var stubSourceCode = MockRepository.GenerateStub<ISourceCodeUnitTest>();
  13.             stubSourceCode.Stub(sc => sc.ValidateString("input string")).Return(false);
  14.  
  15.             ISourceCodeUnitTest obj = new SourceCode();
  16.             Assert.That(obj.MyPublicValidator(stubSourceCode, "input string"), Is.EqualTo("The input is not valid."));
  17.         }
  18.         [Test]
  19.         public void MyPublicValidator_ValidInput_ReturnsValidMessage()
  20.         {
  21.             var stubSourceCode = MockRepository.GenerateStub<ISourceCodeUnitTest>();
  22.             stubSourceCode.Stub(sc => sc.ValidateString("input string")).Return(true);
  23.  
  24.             ISourceCodeUnitTest obj = new SourceCode();
  25.             Assert.That(obj.MyPublicValidator(stubSourceCode, "input string"), Is.EqualTo("The input is a valid string."));
  26.         }
  27.         [Test]
  28.         public static void ValidateString_ValidString_ReturnsTrue()
  29.         {
  30.             ISourceCodeUnitTest obj = new SourceCode();
  31.             Assert.That(obj.ValidateString("valid string"), Is.True);
  32.         }
  33.         [Test]
  34.         public static void ValidateString_InvalidString_ReturnsFalse()
  35.         {
  36.             ISourceCodeUnitTest obj = new SourceCode();
  37.             Assert.That(obj.ValidateString("invalid"), Is.False);
  38.         }            
  39.     }
  40. }

Tuesday, February 15, 2011

Dynamic management of JavaScript file resources in ASP.NET

JavaScript file resource management in "classic" ASP.NET (in my case, ASP 3.0 and .NET Framework 4.0) can be tricky.  Ideally, JavaScript files are loaded only when needed and often must be loaded in a specific sequence to avoid null reference errors.  Without careful management, JavaScript files can be loaded in the wrong sequence because the load order can be at the mercy of the .NET Framework.

Consider a scenario where there is a MasterPage with a ScriptManager, a Web Form with a Script tag and a ScriptManagerProxy, and finally a UserControl on that page with a ScriptManagerProxy.  The concept is that the MasterPage ScriptManager loads JavaScript resources required by every page (e.g., jQuery), but the WebForm and UserControl load JavaScript resources only when needed.  The concept has merit but in this case the design is flawed, because the .NET Framework loads the JavaScript resources in the wrong order – the JavaScript methods for the UserControl will run only if the load order is jQuery, Utilities, Root, WebPage, and CustomControl.

Sample code web page design - MasterPage with ScriptManager; WebForm based on MasterPage with Script tag and ScriptManagerProxy; UserControl on WebForm with ScriptManagerProxy and button

A good solution is to dynamically load all JavaScript resources from the MasterPage ScriptManager control.  This provides fine-grained control over when and in what sequence the JS resources are loaded.  The above scenario is refactored so that the MasterPage still has the ScriptManager and always loads the jQuery and Root JS resources, and the WebForm and UserControl load their JS resources in code instead of by using ScriptManagerProxy and/or Script tag controls.

Master Page design:

  1. <%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site1.master.cs" Inherits="ScriptManagerDemo.Site1" %>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title></title>
  6.     <asp:ContentPlaceHolder ID="head" runat="server">
  7.     </asp:ContentPlaceHolder>
  8. </head>
  9. <body>
  10.     <form id="form1" runat="server">
  11.     <asp:ScriptManager ID="MasterPageScriptManager" runat="server">
  12.         <Scripts>
  13.             <asp:ScriptReference Path="~/Scripts/jquery-1.4.1.min.js" />
  14.             <asp:ScriptReference Path="~/Scripts/Root.js" />
  15.         </Scripts>
  16.     </asp:ScriptManager>
  17.     <div>
  18.         <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
  19.         </asp:ContentPlaceHolder>
  20.     </div>
  21.     </form>
  22. </body>
  23. </html>

Master Page code-behind:

  1. using System;
  2. using Utilities;
  3.  
  4. namespace ScriptManagerDemo
  5. {
  6.     public partial class Site1 : System.Web.UI.MasterPage
  7.     {
  8.         public ScriptReferenceCollectionAdapter ScriptReferences
  9.         {
  10.             get { return new ScriptReferenceCollectionAdapter(this.MasterPageScriptManager.Scripts); }
  11.         }
  12.     }
  13. }

Web Form design:

  1. <%@ Page Title="" Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true" CodeBehind="Working.aspx.cs" Inherits="ScriptManagerDemo.Working" %>
  2. <%@ MasterType TypeName="ScriptManagerDemo.Site1" %>
  3. <%@ Register src="WebUserControlWorking.ascx" tagname="WebUserControlWorking" tagprefix="uc1" %>
  4. <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
  5. </asp:Content>
  6. <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
  7.     <h1>This page is working</h1>
  8.     <uc1:WebUserControlWorking ID="WebUserControlWorking1" runat="server" />
  9. </asp:Content>

Web Form code-behind:

  1. using System;
  2. namespace ScriptManagerDemo
  3. {
  4.     public partial class Working : System.Web.UI.Page
  5.     {
  6.         protected void Page_Load(object sender, EventArgs e)
  7.         {
  8.             this.Master.ScriptReferences.AddScriptAssembly("Utilities", "Utilities.Utilities.js", "jquery-1.4.1.min.js");
  9.             this.Master.ScriptReferences.AddScriptPath("~/Scripts/WebPage.js", "Root.js", true);
  10.             this.Master.ScriptReferences.AddScriptPath("~/Scripts/CustomControl.js", "WebPage.js", true);
  11.         }
  12.     }
  13. }

ScriptReferenceCollectionAdapter class:

  1. using System;
  2. using System.Text;
  3. using System.Collections.ObjectModel;
  4. using System.Web.UI;
  5. using System.Reflection;
  6.  
  7. namespace Utilities
  8. {
  9.     public class ScriptReferenceCollectionAdapter
  10.     {
  11.         private Collection<ScriptReference> ScriptReferences { get; set; }
  12.         private static readonly string _version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
  13.  
  14.         public ScriptReferenceCollectionAdapter(Collection<ScriptReference> scriptReferences)
  15.         {
  16.             ScriptReferences = scriptReferences;
  17.         }
  18.  
  19.         public void AddScriptPath(string path, string afterReferenceName = null, bool forceCacheRefresh = true)
  20.         {
  21.             AddScript(path, null, null, new string[] { afterReferenceName }, forceCacheRefresh);
  22.         }
  23.  
  24.         public void AddScriptPath(string path, string[] afterReferenceNames = null, bool forceCacheRefresh = true)
  25.         {
  26.             AddScript(path, null, null, afterReferenceNames, forceCacheRefresh);
  27.         }
  28.  
  29.         public void AddScriptAssembly(string assembly, string name, string afterReferenceName = null)
  30.         {
  31.             AddScript(null, assembly, name, new string[] { afterReferenceName });
  32.         }
  33.  
  34.         public void AddScriptAssembly(string assembly, string name, string[] afterReferenceNames)
  35.         {
  36.             AddScript(null, assembly, name, afterReferenceNames);
  37.         }
  38.  
  39.         private void AddScript(string path, string assembly, string name, string[] afterReferenceNames, bool forceCacheRefresh = false)
  40.         {
  41.             if (afterReferenceNames == null || afterReferenceNames.Length == 0)
  42.                 ScriptReferences.Add(CreateScriptReference(path, assembly, name, forceCacheRefresh));
  43.             else
  44.             {
  45.                 ScriptReference scriptRef;
  46.                 int si, ai = 0, idx = 0;
  47.                 string refName;
  48.                 do
  49.                 {
  50.                     refName = afterReferenceNames[ai];
  51.                     si = 0;
  52.                     while (si < ScriptReferences.Count && idx == 0)
  53.                     {
  54.                         scriptRef = ScriptReferences[si];
  55.                         if (scriptRef.Name.Contains(refName) || scriptRef.Path.Contains(refName))
  56.                             idx = ScriptReferences.IndexOf(scriptRef) + 1;
  57.                         si++;
  58.                     }
  59.                     ai++;
  60.                 } while (ai < afterReferenceNames.Length && idx == 0);
  61.                 idx = idx == 0 ? ScriptReferences.Count : idx;
  62.                 ScriptReferences.Insert(idx, CreateScriptReference(path, assembly, name, forceCacheRefresh));
  63.             }
  64.         }
  65.  
  66.         private static ScriptReference CreateScriptReference(string path, string assembly, string name, bool forceCacheRefresh)
  67.         {
  68.             if (string.IsNullOrEmpty(path))
  69.                 return new ScriptReference(name, assembly);
  70.             else
  71.             {
  72.                 if (forceCacheRefresh)
  73.                 {
  74.                     path = string.Format("{0}?ver={1}", path, _version);
  75.                 }
  76.                 return new ScriptReference(path);
  77.             }
  78.         }
  79.     }
  80. }

Key Points:

  • Expose an instance of the ScriptReferenceCollectionAdapter class as a public property of the MasterPage (Site1) class.
  • Add a MasterType directive to the WebForm ASPX to gain access to the MasterPage's properties.
  • Load the JavaScript resources in the proper order during the WebForm's Page_Load event.
  • The ScriptReferenceCollectionAdapter provides the necessary methods and logic to load the JS resources in a specific sequence.
  • These code samples can be refactored easily into the MVP design pattern.

A bonus feature to this approach is each JS resource can be loaded with a URL query string to prevent browser caching.  In the code sample above, the query value is set to be the version of the loading assembly, which should change each time the application is deployed.  I got the idea for how to prevent browser caching from this nice blog post Prevent Js and Css Browser Caching Issues with ASP.NET with additional information from this Stack Overflow forum.

Get a code sample of a Visual Studio 2010 .NET 4.0 web application containing all of the above code and additional resources.

Wednesday, February 9, 2011

Javascript Module Pattern and Augmentation

I have been learning about the Module Pattern for JavaScript and have a few notes to share.  Ben Cherry's post on the subject was extremely helpful and my intent here is merely to clarify a few points he made and add the concept of function augmentation.

Consider this code, which demonstrates Global Import, Module Export, and Loose Augmentation per Ben Cherry's post:

  1. /*global jQuery*/
  2. var EXAMPLE = (function (my, $) {
  3.     var _private_string = 'this string is private';
  4.     var _privateFunc;
  5.  
  6.     _privateFunc = function () {
  7.         alert('heads up: ' + _private_string);
  8.     };
  9.  
  10.     my.public_string = 'this string is public';
  11.  
  12.     my.publicFunction = function (input) {
  13.         alert('heads up: ' + my.public_string + input);
  14.     };
  15.  
  16.     return my;
  17. } (EXAMPLE || {}, jQuery));
  18.  
  19. var EXAMPLE = (function (my_root, $) {
  20.     my_root.MyClass = {};
  21.     my_root.MyClass.MySubClassOne = (function () {
  22.         var my = {};
  23.         var _sub_class_one_private = 'very private';
  24.         my.subClassOnePublicFunction = function () {
  25.             return 'public subclass 1';
  26.         };
  27.         return my;
  28.     } ());
  29.     my_root.MyClass.MySubClassTwo = (function () {
  30.         var my = {};
  31.         var _sub_class_two_private = 'also very private';
  32.         my.subClassTwoAugmentation = EXAMPLE.publicFunction;
  33.         EXAMPLE.publicFunction = function (input) {
  34.             my.subClassTwoAugmentation(input);
  35.             alert('EXAMPLE.publicFunction has been augmented');
  36.         };
  37.         return my;
  38.     } ());
  39.     return my_root;
  40. } (EXAMPLE || {}, jQuery));

The first block sets up the EXAMPLE module and the second block uses Loose Augmentation to add additional functions and classes, mimicking the namespacing conventions I am comfortable with from C# development.

Notice the subClassTwoAugmentation method.  This demonstrates how to augment the existing EXAMPLE.publicFunction function with additional code that will run after the original EXAMPLE.publicFunction code has completed.  I needed to learn this trick so that I could chain additional functionality onto an existing event handler in my production code.  I got the idea for this method from Douglas Crockford's JavaScript: The Good Parts in Chapter 4, Augmenting Types, and also from this forum post.

Tuesday, February 8, 2011

Load a Script file from any Location

My organization is developing an ASP.NET application consisting of a parent WAP project with multiple WAP sub-projects.  I ran into a problem loading JavaScript files located in the parent WAP Scripts folder – the sub-project WAPs were not able to load the scripts.

There are two solutions to this problem.  One answer is to use a declarative path in the script tag:

  1. <script type="text/javascript" language="javascript" src="<%=ResolveUrl("~/Scripts/MyScript.js")%>"></script>

A second answer is to use a ScriptManager (or ScriptManagerProxy) control:

  1. <asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
  2.     <Scripts>
  3.         <asp:ScriptReference Path="~/Scripts/MyScript.js" />
  4.     </Scripts>
  5. </asp:ScriptManagerProxy>

Either solution appears to work for any page in the parent or any sub-project WAP.  However, the ScriptManager solution is a more reliable way to ensure that all scripts are loaded in the correct sequence.

Monday, February 7, 2011

7 Javascript Best Practices I Have Learned

In a previous post I listed three books that have been great resources for learning JavaScript.  Here are seven important things I have learned so far in no particular order:

Global variables can quickly become a major design problem in JavaScript.  As a C# developer, at first glance I would assume this code would result in a global object with two private members:

  1. var MyModule = (function () {
  2.     myPrivateProperty = 1;
  3.     myPrivateFunction = function () {
  4.         alert('private');
  5.     };
  6. } ());

In reality, myPrivateProperty and myPrivateFunction both are global variables.  This code properly declares both variables as private:

  1. var MyModule = (function () {
  2.     var myPrivateProperty = 1;
  3.     var myPrivateFunction = function () {
  4.         alert('private');
  5.     };
  6. } ());

There are style conventions to follow in JavaScript.  The ones I list here come from the JavaScript Patterns book by Stoyan Stefanov.

  • Use consistent indentation (typically 4 spaces) to make your code readable.
  • Put the opening curly bracket on the same line as the previous statement to avoid unexpected behavior from implied semicolons.
  • Capitalize constructors using TitleCase, variables and functions using camelCase (or lower_case for variables to make it easier to distinguish from functions), private variables and functions using an opening underscore (e.g., _camelCase), and use all caps for CONSTANT_VALUES.

Comment your code using an API documentation format.  This will allow for auto-generation of code documentation.  I have adopted the YUIDoc tool conventions.

For loops must be coded carefully for maximum efficiency.  As a C# developer, the following code looked good to me:

  1. function forLoopExample() {
  2.     var item;
  3.     var anArray = document.getElementsByClassName('my_class');
  4.     for (i = 0; i < anArray.length; i++) {
  5.         alert(item = anArray[i]);
  6.     }
  7. }

In reality there are several problems with this design:

  • The i variable is an implied global
  • The for loop queries the live DOM on every pass because it checks the element collection's length repeatedly
  • The use of ++ promotes "excessive trickiness"

Here is a much-improved design:

  1. function forLoopExample() {
  2.     var item, i, max;
  3.     var anArray = document.getElementsByClassName('my_class');
  4.     for (i = 0, max = anArray.length; i < max; i += 1) {
  5.         alert(item = anArray[i]);
  6.     }
  7. }

Eval is evil.  The Javascript eval function is a security risk because it grants too much authority to the evaluated string.  A better approach is to use Douglas Crockford's JSON library to evaluate text with JSON.parse() or by using the jQuery.parseJSON() method.

Minify production JavaScript using a tool like Douglas Crockford's JSMin or an online YUICompressor which removes whitespace, comments, etc. thereby significantly reducing the size of your JavaScript file.

Check your JavaScript code using JSLint which is a code quality tool that checks for many common violations such as implied globals, missing semi-colons, unused variables, unreachable code, and many more.

Good Javascript References

I have been learning Javascript as quickly as I can, focusing in particular on best practices and adaptations for .NET development including AJAX via WebMethods.  I have found these three O'Reilly books to be particularly helpful:

  • JavaScript: The Good Parts by Douglas Crockford – great introduction to the most critical core functionality of JavaScript along with tips on what to adopt and what to avoid.
  • JavaScript Patterns by Stoyan Stefanov – design patterns for JavaScript.  The "Gang of Four" Design Patterns book was paramount to making me a better C# developer and I'm hoping that this book will have the same impact on my JavaScript.
  • JQuery Cookbook by Cody Lindley – a perfect complement to the other two books, giving me real-world solutions to many common scenarios while simultaneously bringing me some familiarity with the jQuery library.

Happy reading!

Friday, January 7, 2011

Workaround for batch copy from Find Symbol Results window

Visual Studio 2008 and 2010 do not provide any easy way to export or shift-select the results in the Find Symbol Results window.  I ran into a situation where I needed to copy and paste 550 matches so I needed an automated solution.

I resolved this problem using a simple macro created in Macro Express (which offers a 30 day free trial) to copy one line at a time from the Find Symbol Results window into a Notepad document.  Download a copy of the macro I used here.

Wednesday, January 5, 2011

Resolution of Telerik RadPanel error due to upgrade to .NET Framework 4.0

One of our ASP.NET applications developed a bug after upgrading to the .NET Framework 4.0.  We use a Telerik RadPanel for web site navigation and we discovered that an error would occur when clicking on any RadPanel element, but only when the user opened the site to the default URL without specifying a page.

Example:
http://mysite.com/  - produces the error
http://mysite.com/default.aspx  - no error occurs

Here is the error message:

User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB6.3; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0; .NET4.0C; .NET4.0E)
Timestamp: Wed, 29 Dec 2010 20:43:39 UTC
Message: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.
Details: Error parsing near '
<!DOCTYPE html P'.
Line: 6
Char: 84093
Code: 0
URI: http://mysite.com/Telerik.Web.UI.WebResource.axd?_TSM_HiddenField_=ctl00_RadScriptManager1_TSM&compress=1&_TSM_CombinedScripts_=%3b%3bSystem.Web.Extensions%2c+Version%3d4.0.0.0%2c+Culture%3dneutral%2c+PublicKeyToken%3d31bf3856ad364e35%3aen-US%3a1f68db6e-ab92-4c56-8744-13e09bf43565%3aea597d4b%3ab25378d2%3bTelerik.Web.UI%3aen-US%3af4b6e7a5-697d-4c2b-91aa-e1490df08c78%3a16e4e7cd%3af7645509%3aed16cbdc%3a24ee1bba%3a1e771326%3a4cacbc31

The solution is to add a "managedHandler" preCondition attribute to the Telerik RadCompression module in the web.config file, system.webserver/modules section.

  <system.webServer>
<
validation validateIntegratedModeConfiguration="false"/>
<
modules>
<
remove name="RadUploadModule"/>
<
add name="RadUploadModule" type="Telerik.Web.UI.RadUploadHttpModule" preCondition="integratedMode"/>
<
remove name="RadCompression"/>
<
add name="RadCompression" type="Telerik.Web.UI.RadCompression" preCondition="integratedMode,managedHandler"/>
</
modules>
</system.webServer>


Here are links to the articles that describe the problem and solution, give more information on the "add" element, and provide a great explanation of IIS7 PreConditions.

Monday, January 3, 2011

Leverage Web.config Transformation and external config files

I recently educated myself on this topic from several good online articles including MSDN and blog posts from Scott Guthrie, Scott Kirkland and Vishal Joshi.  The only major limitation I can discover in this wonderful new feature is that the web.config transformation is not invoked by debugging in Visual Studio, as explained in this asp.net forums post and elsewhere.

This limitation poses a problem for my shop and I’m sure is a frustration for any other shop with multiple developers and source code control.  In our case, we have the following requirements:

  • A set of “shared” appSettings that do not change regardless of deployment environment (debug/staging/release etc.)
  • A set of appSettings that do change for each deployment environment
  • Connection strings that change for each deployment environment

The appSettings that do change for each environment and the connection strings need careful management in source code control to avoid conflicts.  Initially I had planned to put the “shared” appSettings into the primary web.config file and then append the variable appSettings using the transformation process.  My primary web.config file and deployment environment transformation config files all would be managed using source control, and in addition each developer would have a sandbox transformation config file not under source control.  I would use a similar strategy for the connectionStrings section.  Unfortunately this strategy fails because the transformation is not invoked by debugging.

My solution is to use a combination of custom config sections, external config files, and transformation to meet all of my shop’s needs.  My solution results in more files to manage than the setup I wanted to use, but is still very manageable. 

This post provides a simplistic example with only Debug and Release configurations, but this concept can be scaled to any number of build configurations.  Here is a screen shot of a sample solution:

SolutionExplorer

Here is the Web.config file, which is used for Visual Studio debugging.  Note the custom “sharedSettings” section containing key/value pairs that do not change regardless of deployment environment and the use of external config files for the appSettings and connectionStrings sections.

<?xml version="1.0"?>
<
configuration>
  <
configSections>
    <
section name="sharedSettings" type="System.Configuration.NameValueFileSectionHandler"/>
  </
configSections>
  <
connectionStrings configSource="ConfigFiles\connectionStrings.Debug.config" />
  <
appSettings configSource="ConfigFiles\appSettings.Debug.config" />
  <
sharedSettings>
    <
add key="SharedKeyOne" value="Shared1" />
  </
sharedSettings>
  <
system.web>
    <
compilation debug="true" targetFramework="4.0" />
  </
system.web>
  <
system.webServer>
     <
modules runAllManagedModulesForAllRequests="true"/>
  </
system.webServer>
</
configuration>

Here is the Web.Release.config transformation file – transformations are used to update the connectionStrings, appSettings, and compilation debug settings.

<?xml version="1.0"?>
<
configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<
connectionStrings xdt:Transform="Replace">
<
add name="MyConnection" connectionString="my RELEASE connection string" providerName="System.Data.SqlClient"/>
</
connectionStrings>
<
appSettings xdt:Transform="Replace">
<
add key="KeyOne" value="Release1" />
<
add key="KeyTwo" value="Release2" />
</
appSettings>
<
system.web>
<
compilation xdt:Transform="RemoveAttributes(debug)" />
</
system.web>
</
configuration>

The Web.config and Web.Release.config files are managed under source code control.  The appSettings.Debug.config and connectionStrings.Debug.config files are customized locally by the developer and are not managed under source code control.