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)

All blog platforms send out pings using the XML-RPC protocol whenever a new post is created or an old one is updated. It is very simple to send out XML-RPC pings using C#, because it is just a normal HTTP request with some XML in the request body. See here how to ping using C#.

To send a ping is the client part of the transaction. There must also be a server to intercept the ping – an endpoint. Feedburner is probably the most widely used by blogs at the moment. What happens is that when you write a post, the blog engine sends a ping to Feedburner’s XML-RPC endpoint. In the XML body of the ping request is the URL of your blog so Feedburner knows who sent the ping. It then retrieves your blog’s RSS feed, parses it, and spits it out to your readers. That’s how simple and powerful it is to use XML-RPC pings.

In an earlier post I showed you how to write the ping client, now let’s look at the server or endpoint. The endpoint can be used by any service that needs to be notified whenever content is updated on a website. That goes for all applications that listen to RSS feeds for instance.

The code

In 100 lines of code including comments, we are able to build an XML-RPC ping server endpoint. All it has to do is to listen for POST requests and parse the XML sent in the request body as shown in my earlier post. By parsing the XML it can find the URL that has been updated. The method HandleUrl is where the URL is passed to and it’s from here you write your logic to handle the ping request.

#region Using

 

using System;

using System.IO;

using System.Net;

using System.Xml;

using System.Web;

 

#endregion

 

public class XML_RPC : IHttpHandler

{

 

  /// <summary>

  /// Handles the URL that was sent by the ping request.

  /// </summary>

  private void HandleUrl(Uri url)

  {

    //This is where you write the code for handing the incoming URL.

    //It is the only place you need to change.

    HttpContext.Current.Response.Write(url);

  }

 

  #region Parse the request

 

  /// <summary>

  /// Parses the request body and returns the URL.

  /// </summary>

  private static string ParseXml(string xml)

  {

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(xml);

 

    string methodName = doc.SelectSingleNode("//methodName").InnerText;

    if (methodName == "weblogUpdates.ping")

    {

      XmlNodeList values = doc.SelectNodes("//value");

      if (values.Count == 2)

      {

        return values[1].InnerText;

      }

    }

 

    throw new NotSupportedException("'" + methodName + "' XML-RPC method not supported");

  }

 

  /// <summary>

  /// Receives this content from the current HTTP request stream.

  /// </summary>

  public static string ReceiveBody()

  {

    using (StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream))

    {

      return reader.ReadToEnd();

    }

  }

 

  #endregion

 

  #region IHttpHandler members

 

  /// <summary>

  /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"></see> instance.

  /// </summary>

  /// <value></value>

  /// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>

  public bool IsReusable

  {

    get { return false; }

  }

 

  /// <summary>

  /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.

  /// </summary>

  /// <param name="context">An <see cref="T:System.Web.HttpContext"></see> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>

  public void ProcessRequest(HttpContext context)

  {

    string body = ReceiveBody();

 

    if (!string.IsNullOrEmpty(body))

    {

      try

      {

        string parsedUrl = ParseXml(body);

        Uri url;

 

        if (Uri.TryCreate(parsedUrl, UriKind.Absolute, out url))

          HandleUrl(url);

      }

      catch (Exception ex)

      {

        // Put whatever error handling you like here.

        context.Response.Write(ex.Message);

      }

    }

  }

 

  #endregion

 

}

Implementation

To use this XML-RPC server class you need to download the class below and put it in your App_Code folder. Then you need to register the handler in the web.config like so:

<httpHandlers>
  <add verb="*" path="xml-rpc.axd" type="XML_RPC" />
</httpHandlers>

The endpoint will then be located at example.com/xml-rpc.axd. The last thing you need is to add some logic to the HandleUrl method of the class and you are ready to go.

XML-RPC.zip (1,18 kb)