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.
Other Posts you might also like