Thursday, January 3, 2008

Application Root is your friend

It still surprises me how many ASP.NET developers I run into don't know about the different ways to construct path references in ASP.NET.  Let's say we want to include an image in our website.  This image is hosted on our website, in an "img" subfolder off of the application root.  So how do we create the image HTML, and what do we use as the URL?  The wrong answer can lead to big-time maintenance headaches later.

There are three kinds of paths we can use:

  • Absolute
  • Relative
  • Application root (ASP.NET only)

Additionally, we have a few choices on how we chose to create the image in our ASP.NET page:

  • Plain ol' HTML
  • HTML server control
  • Web server control

Each kind of path can be used for each rendering object type (HTML, server control).  It turns out that the path is much more important than the rendering object, as different forces might lend me to use controls over HTML.  For posterity, I'll just pick plain ol' HTML as an example.

Absolute

Absolute paths are fully qualified URL paths that include the domain name in the URL:

<img src="http://localhost/EcommApp/img/blarg.jpg" />

Absolute paths work great for external resources outside of my website, but are poor choices for internal resources.  Typically ASP.NET development is done on a development machine, and deployed to a different machine, which means the URLs will most likely change.

For example, the URL above works on my local machine, but breaks when deployed to the server because the "EcommApp" now resides at the root, so I need a URL like "http://ecommapp.com/img/blarg.jpg".  Since this absolute path is different, my link breaks, and I have to make lots of changes going back and forth between production and development.  For internal resources, absolute paths won't work.

Relative

Relative paths don't specify the domain name, and come in a few flavors:

  • Site-root relative
  • Current page relative
  • Peer relative

These URL path notations are similar to file path notations.  Each is slightly different and carries its own issues.

Site-root relative

Here's the same img tag used before, now with a site-root relative path:

<img src="/EcommApp/img/blarg.jpg" />

Note the lack of the domain name and the leading slash, that's what makes this a site-root relative path.  These paths are resolved against the current domain, which means I can go from "localhost" to "ecommapp.com" with ease.

Again, the problem I run into is that locally, my app is deployed off of an "EcommApp" folder, but on the server, it's deployed at the root.  My image breaks again, so site-root relative paths aren't a great choice, either.

Current page relative

Now the img tag using a current page relative path:

<img src="img/blarg.jpg" />

This time, I don't have the leading slash, nor do I include the "EcommApp" folder.  This is because current page relative paths are constructed off the URL being requested, which in this case is the "default.aspx" page at the root of the application.  The request goes from the "default.aspx" path, wherever that might be.  Now my URL does not have to change when I deploy to production, it works in both places.

But I have two problems now:

  • Moving the page means I have to change all of the resource URLs
  • Creating a page in a subfolder means all URLs to the same resource could be different

This leads me to the last kind of relative path.

Peer relative

Suppose I want to create the img tag in a site with the following structure:

  • \Root
    • \img
      • blarg.jpg
    • \products
      • default.aspx

Note that default.aspx has to go up one node, then down one node to reference the file in the above tree.  Here's the img tag to do just that:

<img src="../img/blarg.jpg" />

Similar to folder paths, I use the ".." operator to climb up one node in the path, then specify the rest of the path.  This path works just fine in production and development, but I still have two main problems:

  • URLs to the same resource are different depending on depth of the source file in the tree
  • Moving a resource forces me to manually fix the relative paths

If I decide to move the "default.aspx" page up one level, all of the relative paths must be manually fixed.

But there's one more major issue.

User controls

Now let's suppose I have the following setup:

  • \Root
    • \img
      • blarg.jpg
    • \products
      • default.aspx
    • \user
      • login.aspx
    • \support
      • help.aspx
    • \usercontrols
      • header.ascx
    • default.aspx

All of the ASPX files use the same "header.ascx" control (I'm not using master pages on this site).  The "header.ascx" control needs to reference the img, but note that the relative path is calculated based on the page requested, not the user control requested.  This means that the relative URL will only work if the user control just happens to be included in a page at the correct depth.  All other times it will break, and this is a huge problem.

Luckily, ASP.NET includes a handy way to fix all of these problems, deployment and otherwise.

Application root

A path built with an application root is prefixed with a tilde (~).  For example, here's a raw application path to the image:

~/img/blarg.jpg

Note the "~/" at the front, that's what signifies it as an application root path.  Application root paths are constructed starting at the root of the application.  For example, both "http://ecommapp.com" and "http://localhost/EcommApp" are application roots, so I don't have to worry about changing paths at deployment.

Additionally, I don't have to worry about problems with node depth in the hierarchy, as paths are formed from the root and not relative to a leaf node, so my user control problem disappears.

One issue with application root paths is only ASP.NET knows about them.  Not browsers.  If I do this:

<img src="~/img/blarg.jpg" />

The image breaks, as browsers don't know what IIS applications are, they just know URLs.  ASP.NET, however, will take this URL and generate the correct relative path for you, as long as I use ASP.NET to generate the path.  Server controls, like "asp:hyperlink" can handle the application root path.

To use the application root path in raw HTML, I just need to use the ResolveUrl method, which is included in the Control class, and therefore available in both my Page and UserControl classes.  Combining raw HTML and the ResolveUrl method, I get:

<img src="<%= ResolveUrl("~/img/blarg.jpg") %>" />

The "<%= %>" construct is basically a "Response.Write", and allows me to call the ResolveUrl method directly.

Using application root paths allows me to:

  • Develop locally and deploy to production seamlessly
  • Have consistent URL per resource
  • Use raw HTML without the problems of absolute and relative paths

Some caveats

No matter what I do, I have to change code if either the resource or the page moves.  I can minimize the number of changes by externalizing the specific path (the "~/img/blarg.jpg" part) to a resource file, constant, or static global variable.  This applies for all types of paths, so I like to eliminate as much duplication as possible.

It's dangerous to assume that the structure or names of resources and pages won't change at some point.  As a web site grows, it can become necessary to move resources around and reorganize your site structure.  To minimize the impact of deployment and change, use application paths as much as possible, you'll save on Excedrin later.

Time expired, still going strong

My Live Writer beta expired at the beginning of the year, but I'm still going strong:

I don't see the "Ask Me.....Never" button, so I'll just click the Later button.  It's still less work to click a button once a day than it is jumping through the hoops to get Live Writer installed on a Server 2003 machine.  I still love the LW, so I'll keep clicking away.

Wednesday, January 2, 2008

Targeting multiple environments through NAnt

One of the nice things about using a command-line local build is that I can easily target multiple environments.  Our configuration scheme is fairly straightforward, with all changes limited to one "web.config" file.

When I refer to multiple environments, I'm talking about many individual isolated deployment targets, such as production, integration, developer, local, etc.  Each environment has its own database, services, maybe even domain.  Sometimes I need to configure my local code to point to different environments, where maybe a defect shows up in production but not our integration environment.

A typical scenario might be that I have different database in each environment.  Different databases means different connection strings, and my connection strings are stored in my "web.config" file.  The problem is that the "web.config" file is stored in source control, and I don't want to always check-in and check-out the file each time I want to target a different environment.

Additionally, I don't want to have to remember the connection string when I switch to a different environment.  I want it all automated, and I want it to just work.

To point our local codebase at different environments, we apply a few tricks to NAnt to make it easy to switch back and forth between many environments.

The command-line build

The first item we have set up is a command-line build and local deployment.  Our environment is a too complex to have only solution compilation to be sufficient to actually run our app, so we use NAnt to build and run our software.  To do this, I have a very simple "go.bat" batch file that calls NAnt with the appropriate command-line arguments:

@tools\nant\NAnt.exe -buildfile:NBehave.build %*

When I call NAnt from the command-line, I can pass in multiple targets without needing to specify the build file or other arguments every time:

go clean test deploy

Now that I can easily call different targets in the build, I can use that mechanism to target different environments, doing something like this:

go PROD clean test deploy

Configuring NAnt

To get a NAnt target to change my configuration, I need a few elements in place:

  • File to hold configuration entries
  • Target to load configuration
  • Tasks to apply configuration

The basic idea is that the "PROD" or "SIT" or "DEV" target will load up specific configuration properties.  After compilation, these configuration properties will be inserted back into the web.config file.  I will have a set of configuration properties for each environment that have the same name, but different values.

Configuration settings file

I like to keep my configuration settings in a separate build file, so I created an "environmentSettings.build" file to hold all of the settings for each environment:

<?xml version="1.0" encoding="utf-8"?>
<project name="Environment Settings" xmlns="http://nant.sf.net/schemas/nant.xsd">

  <target name="config-settings-PROD">
    <property name="connection_string" value="Data Source=prddbsvr;Initial Catalog=AdventureWorks;Integrated Security=true" />
  </target>

  <target name="config-settings-SIT">
    <property name="connection_string" value="Data Source=sitdbsvr;Initial Catalog=AdventureWorks;Integrated Security=true" />
  </target>

  <target name="config-settings-DEV">
    <property name="connection_string" value="Data Source=(local);Initial Catalog=AdventureWorks;Integrated Security=true" />
  </target>

</project>

Two important items to note are:

  • Target names differ only by last part, the target environment
  • Targets all define the same property, namely "connection_string", but these values are different in each example

Selecting configuration

Now that my configuration settings file is finished, it's time to turn our attention back to the main build script file.  I need to add targets to handle "PROD", "SIT", etc.  Additionally, I want to define a property that has a default environment setting.

The targets that handle "PROD", etc. don't need to do much other than re-define the environment setting property and load the targets from the new file.  Here are those targets:

<property name="target-env" value="DEV" />

<target name="DEV">
  <property name="target-env" value="DEV" />
  <call target="load-config-settings" />
</target>

<target name="SIT">
  <property name="target-env" value="SIT" />
  <call target="load-config-settings" />
</target>

<target name="PROD">
  <property name="target-env" value="PROD" />
  <call target="load-config-settings" />
</target>

<target name="load-config-settings" unless="${target::has-executed('load-config-settings')}">
  <include buildfile="${env-settings.file}" />
</target>

The first thing to note here is the declaration of the "target-env" property at the top.  That will be useful later on when making decisions based on the target environment.

Next, I declare a set of targets named after my target environments, namely "DEV", "SIT" and "PROD".  These are also the same names as the postfixes in the target names in my "environmentSettings.build" file I created earlier.  In each of these targets, I override the "target-env" property with its new value, the target environment.  Remember that in my "go.bat" file, all command-line arguments are targets to be executed by NAnt, so I have to create a specific target for each target environment I want to support.

Finally, I call the "load-config-settings" target.  Its responsibility is simply to load the environment settings build file I created earlier, but not to call any of its targets.  The reason for the "unless" part is that NAnt does not allow you to declare the same targets twice, so I need to make sure that the "load-config-settings" target is only executed at most once.

Loading and applying configuration

Now that I have all of the targets loaded, I need to call the appropriate settings target and apply the configuration properties to the web.config file.  This step is usually done post-compilation, but I can apply the settings any time after they are loaded:

<target name="modify-web-config">
  
  <call target="config-settings-${target-env}" />

  <xmlpoke
    file="${deploy.dir}/Web.Config"
    xpath="/configuration/appSettings/add[@key='ConnectionString']/@value"
    value="${connection_string}"
   />

</target>

First, this target calls "config-settings-XXXXX", where the last part is filled in by the "target-env" property declared earlier.  If I chose "SIT", the "config-settings-SIT" target is called.  If I chose "PROD", the "config-settings-PROD" target is called.  Recall also that the "config-settings-XXXX" targets all declare the same properties, but with different values.

Finally, I use the xmlpoke task to modify the web.config file, giving it the new "connection_string" property value set up from the "config-settings-XXXX" target.

Now, if I want to target different environments, all I need to do is put in the environment name when calling the batch script, such as "go SIT deploy-local", and my local app now targets a different environment.  If there are more complex things I need to do based on the target environment, all I need to do is check the "target-env" property.

Wrapping it up

There are many different ways to target different environments, such as web deployment projects and solution configuration.  I found using NAnt integrated well with our command-line build and gave us a maintainable solution, as all build/deployment logic is hosted in one build script, instead of spread over many project or solution configurations.

Thursday, December 20, 2007

Upgrading to Windows XP SP2

After months of soul-searching, I made the gut-wrenching decision today to upgrade my home PC to Windows XP SP2.

Upgrade from Vista, that is.

I'm completely convinced that Vista is not designed to run on single-core/processor machines.  I've run Vista on work machines without any hiccups, with Aero Glass going full on.  I thought I had a semi-decent home PC:

  • AMD Athlon XP 2800+
  • 2 GB RAM

Alas, it was not enough to net me more than about 2.9 on the Windows Experience Index.  UAC annoys the hell out of me, most file operations take forever, I'm denied access to do simple operations, like creating a folder on my D: drive.  At work, I'll turn all of these safety features off, as I'm okay running with scissors in a development environment.  I have no idea how a home user deals with all of it, I sure couldn't.  Hopefully Vista's SP1 will fix these issues.

Tuesday, December 18, 2007

Extension methods and primitive obsession

In another water-cooler argument today, a couple of coworkers didn't like my extension method example.  One main problem is that it violates instance semantics, where you expect that a method call off an instance won't work if the instance is null.  However, extension methods break that convention, leading the developer to question every method call and wonder if it's an extension method or not.  For example, you can run into these types of scenarios:

string nullString = null;

bool isNull = nullString.IsNullOrEmpty();

In normal circumstances, the call to IsNullOrEmpty would throw a NullReferenceException.  Since we're using an extension method, we leave it up to the developer of the extension method to determine what to do with null references.

Since there's no way to describe to the user of the API whether or not the extension method handles nulls, or how it handles null references, this can lead to quite a bit of confusion to clients of that API, or later, those maintaining code using extension methods.

In addition to problems with dealing with null references (which Elton pointed out, could be better handled with design-by-contract), some examples of extension methods online propose examples that show more than a whiff of the "Primitive Obsession" code smell:

Dealing with primitive obsession

In both of the examples above (Scott cites David's example), an extension method is used to determine if a string is an email:

string email = txtEmailAddress.Text;

if (! email.IsValidEmailAddress())
{
    // oh noes!
}

It's something I've done a hundred times, taking raw text from user input and performing some validation to make sure it's the "right" kind of string I want.  But where do you stop with validation?  Do you assume all throughout the application that this string is the correct kind of string, or do you duplicate the validation?

An alternative approach is accept that classes are your friend, and create a small class to represent your "special" primitive.  Convert back and forth at the boundaries between your system and customer-facing layers.  Here's the new Email class:

public class Email
{
    private readonly string _value;
    private static readonly Regex _regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");

    public Email(string value)
    {
        if (!_regex.IsMatch(value))
            throw new ArgumentException("Invalid email format.", "value");

        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

    public static implicit operator string(Email email)
    {
        return email.Value;
    }

    public static explicit operator Email(string value)
    {
        return new Email(value);
    }

    public static Email Parse(string email)
    {
        if (email == null)
            throw new ArgumentNullException("email");

        Email result = null;

        if (!TryParse(email, out result))
            throw new FormatException("Invalid email format.");

        return result;
    }

    public static bool TryParse(string email, out Email result)
    {
        if (!_regex.IsMatch(email))
        {
            result = null;
            return false;
        }

        result = new Email(email);
        return true;
    }
}

I do a few things to make it easy on developers to use an email class that can play well with strings as well as other use cases:

  • Made Email immutable
  • Defined conversion operators to and from string
  • Added the Try-Parse pattern

The usage of the Email class closely resembles usage for other string-friendly types, such as DateTime:

string inputEmail = txtEmailAddress.Text;

Email email;

if (! Email.TryParse(inputEmail, out email))
{
    // oh noes!
}

txtEmailAddress.Text = email;

Now I can go back and forth from strings and my Email class, plus I provided a way to convert without throwing exceptions.  This looks very similar to code dealing with textual date representations.

Yes, but

The final Email class takes more code to write than the original extension method.  However, now that we have a single class that plays nice with primitives, additional Email behavior has a nice home.  With a class in place, I can now model more expressive emails, such as ones that include names like "Ricky Bobby <ricky.bobby@rb.com>".  Once the home is created, behavior can start moving in.  Otherwise, validation would be sprinkled throughout the system at each user boundary, such as importing data, GUIs, etc.

If you find yourself adding logic to primitives to the point of obsession, it's a strong indicator you're suffering from primitive obsession and a nice, small, specialized class can help eliminate a lot of the duplication primitive obsession tends to create.

Dead Google Calendar gadget

This morning I received an interesting yet disturbing message on the Google Calendar gadget on my iGoogle home page:

Great gadget that it was, I think I might be a little more discerning about what gadgets I put on the home page.  Word of warning, you probably don't want to google "donkey-punching", definitely NSFW.  It looks like Google changed something, broke the gadget, and the gadget author decided to let everyone know, through an....interesting means.

ALT.NET summary blog

If the ALT.NET mailing list is too much to keep up with, as it is the Mother of All Firehoses (MOAF), several folks have pointed out a nice summary blog:

Alt.Net Pursefight!

It keeps a nice daily ego check and blow-by-blow commentary of some of the more interesting comment wars going on there.  Pretty funny.