Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Static Singletons for ASP.NET Controls


:P
On this page:

When building generic ASP.NET Server controls that also provide a sort of API service to other custom controls or page level code, it's often necessary to ensure that only a single instance of a control exists, and that only that single instance of this control or component can be accessed in the context of an ASP.NET request. While this is usually easy to accomplish in actual page code (since you can reference the control or component explicitly), if the API is also consumed by other Server Controls it's crucial that it's easy and efficient to find and access that single instance of a control effectively. This isn't a typical scenario, but it's a common pattern for controls that expose API level functionality in addition to the actual control based markup.

This isn't a new concept (Simone had a great post on this some time ago), but I've run into this pattern so many times and have also gotten a number of questions about this that I thought I'd revisit it here since I just went through the excercise again.

For example, a few days ago I talked about a ScriptContainer control that's akin to the ASP.NET ScriptManager control, but with a few additional features. The idea is that there's a single level API that can be used to retrieve an instance to the ScriptContainer, so that code either in CodeBehind or a customer Server Control has access to that instance sitting on a page.

So, in this example I can reference the wwScriptControl on the page with code like this:

wwScriptContainer script = wwScriptContainer.Current;
script.AddScript("~/scripts/wwEditable.js", true);

and always be guaranteed that I get that single instance of the control whether it's already embedded in the page markup or if not it's created on the fly and returned to me. Incidentally the ASP.NET ScriptManager also does something similar with its GetCurrent() method, but it only returns an instance if there's already one loaded - it's your responsibility to create one and add it to the page otherwise. The wwScriptContainer.Current property here instead always returns a control instance including creating one and adding it to the page when none exists yet.

Another example, where this applies is my ClientScriptProxy API, which returns a single instance of the ClientScriptProxy object that references either a ScriptManager if one is on the page, or otherwise talks to the Page.ClientScript object and so provides the same behavior regardless of which API is available. In this case too I need to ensure that only a single instance of this object exists and that I can always retrieve that single instance from anywhere in code. The ClientScript.Current property serves exactly this purpose.

ASP.NET Singletons and HttpContext.Current

This Singleton pattern can be applied to any ASP.NET control (or even component or Page) that in addition to a markup based control interface also exposes a generic API that scoped to the current ASP.NET request/page executing. In ASP.NET request level state is managed through the HttpContext object which is yet another example of a Singleton.

The basic concept of a Singleton is very straight forward. Here's Simone's example which is as concise as it gets:

public class SingletonPerRequest
{
    public static SingletonPerRequest Current
    {
        get
        {
            return (HttpContext.Current.Items["SingletonPerRequest"] ??
                (HttpContext.Current.Items["SingletonPerRequest"] =
                new SingletonPerRequest())) as SingletonPerRequest;
 
        }
    }
}

where the SingletonPerRequest() creates whatever object reference you need to create. The key thing is that the property is static and that it returns a value that is stored on the HttpContext.Items collection.

To understand how this works it's important to understand how the HttpContext object relates to the ASP.NET request flow. HttpContext.Current represents the current ASP.NET Request context and this context is available throughout the course of an ASP.NET request starting with the HttpApplication.BeginRequest pipeline event all the way through HttpApplication.EndRequest(). It also includes module and handler execution which is also part of the application pipeline.

ne important aspect of the HttpContext object is that it also contains an Items collection - an in-process statebag that allows you to attach arbitrary state at any point in processing of a request and access it later. Because HttpContext.Current is static and always accessible any state stored in Items is always available (unless explicitly released) until the end of the request. It's an ideal store for request specific information like for example store an instance reference to a control that should only be accessed through a single reference. IOW, a Singleton.

Long story short for my ScriptContainer example, I can use HttpContext.Current.Items["wwScriptContainer"] to save and instance of the script container control and then implement a custom method or property like .Current to retrieve that single instance.

Here's what this code looks like for ScriptContainer:

public ScriptContainer()
{
    this._Scripts = new List<HtmlGenericControl>();
    this._InternalScripts = new List<ScriptItem>();
 
    if(HttpContext.Current != null)
    {                
        // *** Save a Per Request instance in Context.Items so we can retrieve it
        //    generically from code with wwScriptContainer.Current
        if (HttpContext.Current.Items.Contains("ScriptContainer"))
            throw new InvalidOperationException("Only one wwScriptContainer is allowed per page.");
 
        HttpContext.Current.Items["ScriptContainer"] = this;
    }
 }
 
 public override void Dispose()
 {
    if (HttpContext.Current != null)
        HttpContext.Current.Items.Remove("ScriptContainer");
 }
 
 /// <summary>
 /// Returns a current instance of this control if an instance
 /// is already loaded on the page. Otherwise a new instance is
 /// created, added to the Form and returned.
 /// 
 /// It's important this function is not called too early in the
 /// page cycle - it should not be called before Page.OnInit().
 /// 
 /// This property is the preferred way to get a reference to a
 /// wwScriptContainer control that is either already on a page
 /// or needs to be created. Controls in particular should always
 /// use this property.
 /// </summary>
 public static ScriptContainer Current
 {
    get
    {
         // *** We need a context for this to work!
        if (HttpContext.Current == null)
            return null;
 
        ScriptContainer ctl = null;
 
        if (HttpContext.Current != null)
        {
            // *** Retrieve the current instance
            ctl = HttpContext.Current.Items["ScriptContainer"] as ScriptContainer;
            if (ctl != null)
                return ctl;
        }
 
        ctl = new ScriptContainer();
 
        Page page = HttpContext.Current.Handler as Page;
        if (page == null)
            throw new InvalidOperationException("ScriptContainer.Current only works with Page based handlers."); 
 
        page.Form.Controls.Add(ctl);
  
        return ctl;
    }
 }

The idea is that during initialization of the control the control/component is immediately pushed into a Context.Items item and saved. This instance becomes that single instance accessed. In fact, the constructor explicitly checks for multiple instances and throws if more than one is instantiated explicitly. This means you couldn't have multiple markup controls on the page or a markup control and manually create an instance.

Once stored the Item stored reference - if one exists - can then be retrieved in the .Current property getter which checks to see if the item exists and returns it.

Typically the .Current getter is also responsible for creating a new instance if one doesn't exist, but how that's accomplished or whether this is supported can vary wildly especially with controls. Here the code creates a generic instance and adds it to the ASP.NET form's Controls collection. Note that from within a static property you won't have access to the Page instance (ie. no this.Page) so you have to access the Page through the HttpContext.Current.Handler and cast that to a Page object.

But again how that works will depend on your specific scenario. The issue is that if you have a control there may be a number of configuration settings that must be set in order to create an instance 'generically'. In the case of the ScriptContainer a generic version is acceptable, but with other controls configuration may be required. You may also need to know whether the control was returned from an existing instance or whether a new instance was created and so you might have an additional property that returns this fact. Or you can return null from .Current and then explicitly let client code create the new instance and configure it.

Typically Singletons have to worry about potential multi-user issues, but in ASP.NET request scenarios using HttpContext.Current this isn't an issue since the instance stored on .Current is visible only to the current request, which is of course running on a single thread. So multi-threading and locking is not something you have to worry about.

This Singleton like approach is a pattern I use a lot for ASP.NET Controls that I create - it's a very useful tool for caching expensive construction and ensuring that you truly are only accessing a single instance of a control or component. But you can also apply this same sort of thing to Page level code or even request level code.

If you want to take a closer look at the  ScriptContainer component you can find it as part of the West Wind Ajax Toolkit download.

Posted in ASP.NET  CSharp  

The Voices of Reason


 

Stephen Patten
July 11, 2008

# re: Static Singletons for ASP.NET Controls

Hey Rick,

I was just bit by the HTTPContext.Current had to switch to HTTPRuntime??? it's late, but anyways it has to do with threading, you may not alway have "Current" if your nor carefull. Check out Scott Allen for details.

Regards,
Stephen

Rick Strahl
July 11, 2008

# re: Static Singletons for ASP.NET Controls

@Stephen - HttpContext.Current will always be available as long as you are running inside of an ASP.NET request. There are scenarios where you have to watch out for this - especially with controls that run in the designer. When you do there's no Context. Other issues are if you are running pre-request processing like in HttpApplication.Init where no context is availble. For controls that should never be an issue.

Richard Deeming
July 11, 2008

# re: Static Singletons for ASP.NET Controls

One problem with this approach is if you use Server.Transfer (or cross-page postbacks) after creating your singleton. The new page will use the same HttpContext, which already contains your key, but the control won't exist in the control tree for the new page. I would be inclined to move the singleton instance to the Page.Items collection instead.

Rick Strahl
July 11, 2008

# re: Static Singletons for ASP.NET Controls

@Richard - hadn't thought of that. The problem with Page.Items is that you can't easily put the instance into items in the constructor because Page.Items may not be initialized yet. I have to check this though.

Kevin Dente
July 11, 2008

# re: Static Singletons for ASP.NET Controls

FWIW, ScriptManager uses Page.Items to store itself. It registers itself in OnInit.

I'm not a fan of the register-in-the-constructor approach myself - it seems cleaner to me for the limit to be applied when the control is added to the page (after all, the general idea is one-per-page, right?). The InfoPath forms control uses the constructor/contextitems approach, and it's caused us much grief (though probably more because they wrote it stupidly).

Andy Miller
July 11, 2008

# re: Static Singletons for ASP.NET Controls

I think you worded an important sentence oddly, "So multi-threading and locking is not something you don't have to worry about." Did you mean, "So multi-threading and locking is not something you have to worry about."?

BTW, thank you for writing about the ASP.NET stack. Too much attention seems to be focused on MVC.

Rick Strahl
July 11, 2008

# re: Static Singletons for ASP.NET Controls

@Kevin, Richard - Ok so I took a look at using Page.Items and that works too including cross page with Server.Transfer/Execute. But as you mentioned it requires that OnInit() is used to add the control since in the constructor there's no Item collection yet. This in turn requires though that the object goes through the page cycle - ie. that it's truly added to the Page or Form explicitly. Works here, but in other scenarios it may end up not working.

As to when the 'Singleton' is applied I think it's fairly safe to do this at OnInit() time which means it effectively goes through the Page cycle, but again that's not always possible if you end up having a component that doesn't derive from Control (for example the ClientScriptProxy I mentioned).

@Andy - thanks for catching the typo. Fixed.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024