I’ve been twittering about that I enabled Visual Studio to nest .css and .js files under a user control just like its .ascx.cs file is. That received some attention from people that wanted to know how to do it, so here it is.

The reason why it did it was to increase maintainability by treating user controls more as self contained components. Often, the same stylesheet and JavaScript file is applied to multiple user controls which make it harder to maintain the individual control independent of the rest of the website.

If we can group some of the resources used by a user control together with the user control itself, then we can think of those controls as components and easily move them around – even between projects and solutions because they are self contained.

The code

We can take it a step further and automatically add the .css and .js files bundled with the user control to the page’s head section at runtime. This is done very simply by creating a base class for the user control and add a bit of logic to it. Some user controls are used more than once per page, so it is important to only add the resources once per page. The following base class handles that as well.

#region Using

 

using System;

using System.IO;

using System.Web;

using System.Web.UI;

using System.Web.UI.HtmlControls;

 

#endregion

 

public class BaseUserControl : UserControl

{

 

  /// <summary>

  /// Raises the <see cref="E:System.Web.UI.Control.Load"></see> event.

  /// </summary>

  /// <param name="e">The <see cref="T:System.EventArgs"></see> object that contains the event data.</param>

  protected override void OnLoad(EventArgs e)

  {

    AddRelatedResources();

    base.OnLoad(e);

  }

 

  /// <summary>

  /// Adds the related resources belonging to the user control.

  /// </summary>

  protected virtual void AddRelatedResources()

  {

    if (Context.Items[AppRelativeVirtualPath] == null)

    {

      if (Cache[AppRelativeVirtualPath] == null)

      {

        ExamineLocation(".css");

        ExamineLocation(".js");

      }

 

      string cache = ((string)Cache[AppRelativeVirtualPath]);

 

      if (cache.Contains(".css"))

        AddStylesheet(AppRelativeVirtualPath + ".css");

 

      if (cache.Contains(".js"))

        AddJavaScript(AppRelativeVirtualPath + ".js");

 

      Context.Items[AppRelativeVirtualPath] = 1;

    }

  }

 

  /// <summary>

  /// Examines the location for related resources matching the extension.

  /// </summary>

  /// <param name="extension">The file extension to look for.</param>

  private void ExamineLocation(string extension)

  {

    string stylesheet = Server.MapPath(AppRelativeVirtualPath + extension);

    if (File.Exists(stylesheet))

    {

      Cache[AppRelativeVirtualPath] += extension;

    }

    else

    {

      Cache[AppRelativeVirtualPath] += string.Empty;

    }

  }

 

  /// <summary>

  /// Adds the stylesheet to the head element of the page.

  /// </summary>

  /// <param name="relativePath">The relative path of the stylesheet.</param>

  protected virtual void AddStylesheet(string relativePath)

  {

    HtmlLink link = new HtmlLink();

    link.Href = VirtualPathUtility.ToAbsolute(relativePath);

    link.Attributes["type"] = "text/css";

    link.Attributes["rel"] = "stylesheet";

    Page.Header.Controls.Add(link);

  }

 

  /// <summary>

  /// Adds the JavaScript to the head element of the page.

  /// </summary>

  /// <param name="relativePath">The relative path to the JavaScript.</param>

  protected virtual void AddJavaScript(string relativePath)

  {

    HtmlGenericControl script = new HtmlGenericControl("script");

    script.Attributes["type"] = "text/javascript";

    script.Attributes["src"] = VirtualPathUtility.ToAbsolute(relativePath);

    Page.Header.Controls.Add(script);

  }

 

}

What this example doesn’t do is to group all the stylesheets and JavaScript files into one, so that there will only be one HTTP request. That’s another post, but you can see here how to add multiple stylesheets into one at runtime and then duplicate it for handling multiple JavaScript files as well.

Download

The zip file below contains the base class for user controls as well as a .reg file that will enable Visual Studio to nest .css and .js file under .ascx files. Just double click the .reg file and then place the BaseUserControl.cs in the App_Code folder. You will need to restart Visual Studio after running the .reg file.

The .reg file has only been tested in Visual Studio 2005, but I think if you open it in Notepad you just need to change the version number from 8.0 to 9.0 to make it work in Visual Studio 2008.

Nest css and js.zip (1,09 kb)

A few days ago I needed to write some functionality to fetch an XML document from a URL and load it into an XmlDocument. As always I use the WebClient to retrieve simple documents over HTTP and it looked like this:

using (WebClient client = new WebClient())

{

  string xml = client.DownloadString("http://example.com/doc.xml");

  XmlDocument doc = new XmlDocument();

  doc.LoadXml(xml);

}

I ran the function and got this very informative XmlException message: Data at the root level is invalid. Line 1, position 1. I’ve seen this error before so I knew immediately what the problem was. The XML document that was retrieved from the web had three strange characters in the very beginning of the document. It looks like this:

<?xml version="1.0" encoding="utf-8"?>

Of course that result in an invalid XML document and that’s why it threw the exception. The three characters are actually a hex value (0xEFBBBF) of the preample of the encoding used by the document.

As said, I knew this error and also an easy way around still using the WebClient. Instead of retrieving the document string from the URL and load it into the XmlDocument using its LoadXml method, the easiest way is to retrieve the response stream and use the Load method of the XmlDocument instead. It could look like this:

using (WebClient client = new WebClient())

using (Stream stream = client.OpenRead("http://example.com/doc.xml"))

{     

  XmlDocument doc = new XmlDocument();

  doc.Load(stream);

}

Often there are situations where the WebClient isn’t well suited for this or one might simply prefer to use the WebRequest and WebResponse classes. Still, the solution is very simple. Here is what it could look like:

WebRequest request = HttpWebRequest.Create("http://example.com/doc.xml");

using (WebResponse response = request.GetResponse())

using (Stream stream = response.GetResponseStream())

{

  XmlDocument doc = new XmlDocument();

  doc.Load(stream);

}

This is something that can give gray hairs if you haven’t run into it before, so I thought I’d share.  

If you have any issues with the three preample characters when serving - not consuming - XML documents, then check out Rick Strahl's very informative post about it.