Dan Maharry

Writing about web development since 1997

Today’s Bookmarks (23 Feb 2010)

ASP.NET 4.0, Part 5: I’ll Name That Browser In One…

Welcome to part 5 of my tour through ASP.NET 4.0. In this instalment, we'll cover the updates to ASP.NET’s browser detection information and look at how they’ve made it easier to update that info without their help.

[Updated March 12 2010: Stephen Walther has more information on the updated browser definitions here.]

Browscap.ini

For those of us who remember the golden years days of classic ASP, you’ll also remember browscap.ini. This was (and still is for some developers) the file where a list was kept of all the browsers in use and the capabilities that they each have. Want to know if IE1.5 (yes, 1.5) supported ActiveX? This is where that info was kept and then accessed using the BrowserCapabilities object in code.

Incredibly useful for such a small file (~60KB), it was also in perennial need of update. With each new (version of a) browser, and latterly smartphone, browscap.ini had to be updated. Microsoft’s original intention was to update the file itself, but this quickly didn’t happen, so a number of independent efforts were made. A gentleman named Juan T. Llibre used to do it (I don’t know if he still does) as did a company named CyScape, although they’ve since ceased this to push their browserhawk component instead which actually does more than the ini file can. These days a gentleman named Gary Keith maintains browscap.ini on a weekly basis and maintains a good FAQ on how to use it in your projects.

Browser files

For .NET 2.0, Microsoft shelved browscap.ini in favour of *.browser files. As a browser was identified via its requests to the server, its capabilities are then pushed into a HttpBrowserCapabilities object found a Request.Browser in your code. A great demo of this can be found at http://browserdetection.codeplex.com.

Here are all the *.browser files for .NET 2.0. You’ll find them in C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers.

The .NET 2.0 Browser Files

Far more complicated to maintain than the ini file, the idea is to keep info for each family of browsers in its own file. Default, ie, mozilla and a few others all sound familiar, but Jataayu (now owned by Comviva), Xiino (a browser for Palm OS last updated in 2006) and a few others might not be instantly recognizable.

In .NET 4.0, all the .browser files have been revised and, in some cases, deleted. Now the system knows about today’s mobile phones - iPhones, Windows PhoneOS, Android – and won’t believe that most mobile phones use WAP. All the files for our desktop browsers have been revised too with new ones added for Firefox, Chrome, and Safari.

New Browser files for .net 4.0

Which is good, but how to keep things up to date? Do our .browser files require another Gary Keith?

Keeping Up To Date

There’s always another new browser coming round the corner. In the very near future, IE9, Opera 10.5, Gecko 1.9.3 and the mobile version of IE for Windows 7 Phone will all come out, but the chances are that Microsoft won’t update the *.browsers for us. To keep our applications in the loop then, we have four choices.

  1. Update an existing \ create a new *.browser file and update your entire system
  2. Create a *.browser file and add it to the App_Browsers folder of a particular website, therefore updating just that website.
  3. Extend the existing browser capabilities provider and bend it to your will.
  4. Create your own custom browser capabilities provider and use that instead going forward.

How-tos for all four approaches can be found

In conclusion, keeping up to date with browsers is a hard thing to keep on top of but there are ways and means to do so, thanks to some people’s dedication to the problem, a new set of *.browser files in .NET 4.0 and the addition of an extensibility point into the browser capabilities provider.

Until the next time, happy coding!

Today’s Bookmarks (18 Feb 2010)

ASP.NET 4.0, Part 4: Config Transformation Files

Welcome to part 4 of my tour through ASP.NET 4.0. In this instalment, we'll be looking at a new feature in VS2010 – configuration transformation files. We’ll see the scenario that it addresses, how you’d deal with it in VS2008, how it compares to this part of the new Deployment process in VS2010, and what your options are.

One Configuration File, Many Web Servers

As web developers, we all know that a single web site may well end up deployed on several sets of servers during its development and maintenance lifecycle. At the very least, it will end up on two – your development machine and the live server. (I mean you don’t run your live websites on your dev machine, do you?) In most cases, there will also be a staging server where clients can access sites in development for testing and feedback as well. And with different servers come different database connection strings, different mail server settings, and in fact many other settings stored in your web.config file that you need to change per server.

The Manual Approach

You might try and make these changes manually each time you deploy a new version of the site to a server, perhaps for example having all the correct database connection strings in the web.config file and commenting out all but the correct one for the target servers, once deployed.

<connectionStrings>
  <clear/>
  <add name="DansDb" connectionString="string_to_my_staging_db" />
    <!--
      <add name="DansDb" connectionString="string_to_my_dev_db" />
      <add name="DansDb" connectionString="string_to_my_live_db" />
    -->
</connectionStrings>

This is fine for very simple config files with only or two lines of config to change, but when you’ve even just five separate sections to change per deployment, it gets very tedious and prone to error very quickly. Which is where Web Deployment Projects take over.

Using Web Deployment Projects

An out-of-band release for VS2008 that appeared for download in January 2008, a Web Deployment Project (WDP) provides a configurable post-build step in VS2010 for web developers. Or, in English, adding a WDP to your web project means that you can specify a number of other actions for VS2008 to perform on your code once it has been built, as well as whether it should pre-compiled or not. Crucially to this discussion, this dialog allowed you to tell VS2008 to replace sections in your web.config file with the contents of another file.

The Web Deployment Project dialog for config section replacement

So then, your base web.config would contain the settings for your development environment. Or, if you prefer, links to files where the settings that would change between servers are located. For example,

<appSettings configSource="AppSettings.config"/>
<connectionStrings configSource="ConnectionStrings.config"/>
<system.net>
  <mailSettings>
  <smtp configSource="Web.Mail.config" />
  </mailSettings>
</system.net>

And, as VS2008 goes through its post-build step for a Release build, it changes the references from these development environment files to their live equivalents, as specified in the screenshot above.

<appSettings configSource="AppSettings.Live.config"/>
<connectionStrings configSource="ConnectionStrings.Live.config"/>
<system.net>
  <mailSettings>
  <smtp configSource="Web.Mail.Live.config" />
  </mailSettings>
</system.net>

If you’re feeling adventurous, you could even get VS to delete the development environment files after a release build as well. You just made a few additions to the MSBuild file for the WDP itself, as follows.

<Target Name="AfterBuild">
  <!-- Delete dev and UAT files when deploying live -->
  <Delete Files="$(SolutionDir)..\Release\AppSettings.config" 
    Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />
  <Delete Files="$(SolutionDir)..\Release\ConnectionStrings.config" 
    Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />
  <Delete Files="$(SolutionDir)..\Release\Web.mail.config" 
    Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" />
</Target>

On the whole, this is manageable, assuming you don’t mind the large number of config files you potentially end up with.

Many Configuration Files

If you’re curious to read about all the actions possible with WDPs, please check out Scott Guthrie’s post here. It is targeted at the VS2005 version of WDP but the 2008 version differs only slightly.

Enter MSDeploy and Config Transformation Files

Config transform files (CTFs) are part of the new deployment process and in isolation are set up in a very similar fashion to how WDPs work as detailed above. When you add a web.config file to a web site or web application project, you’ll also see a number of CTFs added too, one for each build configuration you’ve created. If you create any further build configurations after you’ve added web.config to the project, simply right click on web.config in Solution Explorer and select Add Config Transforms. Additional CTFs will be added for each new build configuration.

Adding web.config

The basic strategy for these files is to create a base level of settings in web.config – essentially, what’s needed to get things working in your development environment – and then use the CTF for a specific build configuration to tell VS how to change web.config for that build. Look in Web.Debug.config and you’ll see that no changes will be made. Look in Web.Release.config and you’ll see the following (minus the comments)

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
  </system.web>
</configuration>

An educated guess will correctly tell you that this is an instruction to remove the debug attribute from the <compilation> element whatever its value, thus setting it to the machine-wide default of false.

CTFs share the same schema as standard .NET config files but declare a second namespace which gives you two extra attributes to set down on any element in the file : Locator and Transform.

Locator

Locator identifies the element or attribute to be altered in one of four ways. Its syntax is

Locator = "Command(<parameter>)"

where Command is one of three possibilities

  • Condition : In which case <parameter> is an XPath expression identifying the element or attribute to change relative to the element on which Locator is declared.
  • Match : In which case <parameter> is a comma-delimited list of the attributes on the current elements to be changed
  • XPath : In which case <parameter> is the absolute XPath from the root of web.config to the element to be changed

The fourth way to use Locator is to omit it. If a Transform attribute is set on an element without a Locator, it is applied to that element, as demonstrated in the example straight from Web.Release.config above.

Let’s demonstrate the first three options by using each in turn to do the same task: change some mail settings. In our standard web.config file for development purposes, we use pickupDirectory to test email.

<configuration>
  ...
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="SpecifiedPickupDirectory" from="dan@test.com">
        <specifiedPickupDirectory pickupDirectoryLocation="D:\Temp"/>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

but when deploying live, we want to use an actual mail server and have the mailSettings look like this

<configuration>
  ...
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="network" from="dan@test.com">
        <network host="liveMailServer" />
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

We can achieve this transformation by using Condition to affect all smtp elements with deliverymethod set to SpecifiedPickupDirectory, like so:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="dan@test.com"
            xdt:Locator="Condition(@deliveryMethod='SpecifiedPickupDirectory')"
            xdt:Transform="Replace">
        <network host="liveMailServer"/>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

Using Match rather than Condition, we identify all smtp elements that have a from attribute with value dan@test.com to transform. They key here is that the attribute being matched will not have its value changed.

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="dan@test.com"
            xdt:Locator="Match(from)"
            xdt:Transform="Replace">
        <network host="liveMailServer"/>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

Finally, you can use XPath to specify absolutely the element or group of elements to transform

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="dan@test.com"
            xdt:Locator="XPath(configuration/system.net/mailSettings/smtp
               [@deliveryMethod='SpecifiedPickupDirectory'])"
            xdt:Transform="Replace">
        <network host="liveMailServer"/>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

This simple example perhaps belies the potential on offer here. Because we are using XPath expressions, we can potentially affect groups of elements in web.config rather than just the one, or elements if only certain conditions are met. But aside from a direct replacement as demonstrated above, what other changes can we make?

Transform

The Transform attribute defines exactly what alteration to make to an element. Its syntax is

Transform="Command[(parameter)]"

where command is one of xyz possibilities. (Code samples here are snippets rather than full CTF listings.)

  • Insert : Adds the element it is specified on as the last child to the selected element. For instance, this adds a new element to <appSettings>
<appSettings>
  <add name="LiveSetting" value="Something" 
       xdt:Transform="Insert" />
</appSettings>
  • InsertBefore(XPath) and InserAfter(XPath): Inserts the element it is specified on immediately before/after the element specified by the XPath expression. For example, this adds a <clear /> immediately before a connection string declaration
<connectionStrings>
  <clear xdt:Transform="
    InsertBefore(/configuration/connectionStrings/add[@name='MyDB'])" />
</connectionStrings>
  • Replace : Replaces the selected element with that specified, as demonstrated earlier
  • Remove : Removes the selected element, or the first in the selected group.
  • RemoveAll : Removes all the elements in the selected group. For example, this removes all the <add> elements under <appSettings>
<appSettings>
  <add xdt:Transform="RemoveAll" />
</appSettings>
  • RemoveAttributes(comma-separated list of attributes to remove) : Removes all the specified attributes from the selected elements, as demonstrated in the original Web.Release.config file.
  • SetAttributes(comma-separated list of attributes to add) : Adds the named attributes on the selected element with the values given. For example, this sets the default from address on all mail servers.
<system.net>
  <mailSettings>
    <smtp from="dan@test.com"
          xdt:Transform="SetAttributes(from)">
    </smtp>
  </mailSettings>
</system.net>
  • XSLT(filepath) : Applies an XSLT file to the selected elements.

Phew! I hope you can see how powerful CTFs can be, especially if you use the power of XPath to identify elements to transform. If you’re new to XPath, the w3schools introduction to XPath is a good place to start.  You can find more information and examples on CTF syntax here. Until the next time, happy coding!