Click here to Skip to main content
Click here to Skip to main content

The Virtual Singleton Pattern in C#

By , 29 Sep 2003
 

Introduction

Having read about the virtual singleton pattern (DOC or PDF) at work (Autoscribe), I realized that managing user-specific data centrally could be achieved without the need to explicitly pass an identifying token with every remote method call. This makes the programming interface easier to read and work with. It also centralizes potentially sensitive information which you may not want to pass over a remoting boundary (like passwords, for example), and if used properly can reduce remoting traffic.

What was needed, though, was a way to identify which of the virtual singleton's clients was making the current call. Microsoft's CallContext sample shows the solution to this.

Background

The virtual singleton is essentially an object that can be used as a singleton by several callers, but thestate of the object appears to be caller dependent.

A virtual singleton accessing data dependent on the calling client.

This is done by assigning each caller a unique ID (detailed below) and storing data in a collection keyed by this ID. In the example implementation shown here, the ID is a string representation of a GUID created by the client. The virtual singleton stores a user-name associated with the unique ID by using a dictionary.

The virtual singleton is a remote object accessed through a remoting call to a server process. Remoting calls have an associated 'context' which, in simplistic terms, describes the source of the remoting call. The CallContext allows us to set this once in the client, and it is then available to the server code with every remote method called.

On the client:

CallContext.SetData("USER", new CallContextString("MyString");

On the server:

CallContextString z = (CallContextString) CallContext.GetData("USER");

The CallContextString is a simple class inheriting the ILogicalThreadAffinative interface. In this case I just used the object from Microsoft's CallContext sample.

Using the code

The code consists of three assemblies - familiar to anyone who has experimented with remoting.

The first assembly to note is the server. This is a simple console application that must be run in the background. In a serious project, this would very likely be a Windows service, but for now a console will do. The main code is simply:

try
{
    RemotingConfiguration.Configure("VirtualSingletonServer.exe.config");
}
catch(System.Exception ex)
{
    System.Console.WriteLine("Config not loaded.");
    System.Console.WriteLine(ex.Message);
}
finally
{
    System.Console.WriteLine("Press <ENTER> to close");
    System.Console.ReadLine();
} 

Of course, the main work is done by the config file:

<configuration>
  <system.runtime.remoting>
    <application name="Server">

      <service>
        <wellknown mode="Singleton" 
            type="VirtualSingleton.VirtualSingleton, VirtualSingleton" 
            objectUri="VirtualSingleton.soap" />
      </service>

      <channels>
        <channel ref="http" port="8043" />
      </channels>

    </application>
  </system.runtime.remoting>
</configuration>

The important point to note here is that the VirtualSingleton assembly contains a VirtualSingleton class (yes, I know I should have named them differently to avoid confusion, but it's too late now). The VirtualSingleton class is run as a remoting singleton. See? We don't even have to implement the singleton pattern here, .NET does it for us. Isn't it great?

OK, so now we need to see the VirtualSingleton assembly. This assembly is shared by both the client and the server. There are better/slicker way to do this, but this is only a demo.

The VirtualSingleton assembly contains two classes, the first of which is the previously mentioned CallContextString class. This really is a simple class:

   [Serializable]
    public class CallContextString : ILogicalThreadAffinative
    {
      String _str ="";
      public CallContextString(String str)
      {
        _str = str;
      }

      public override String ToString()
      {
        return _str;
      }
    }

The ILogicalThreadAffinative interface allows this class to be transported between the client and server AppDomains whenever a remoting call is made. In theory you can pass any serializable type along with the context, but remember that this will be sent with every call so large context data will slow things down.

The second class in the VirtualSingleton assembly is the VirtualSingleton class itself. And I bet you thought I'd never get around to it.

The VirtualSingleton is a MarshalByRef based object that contains a StringDictionary called NameDictionary as a private field, and a public property called UserName. The magic happens in the get and set methods of the property.

First, the get:

get
{
    // Retrieve the call context string "USER" for the dictionary key.
    CallContextString z = (CallContextString) CallContext.GetData("USER");
    return NameDictionary[z.ToString()];
}

As you can see, the CallContextString saved as the "USER" data is used as a key to reference the NameDictionary. It's not exactly rocket science.

The set method is a little more complex (but not exactly taxing):

set
{
    // Retrieve the call context string "USER" for the dictionary key.
    CallContextString z = (CallContextString) CallContext.GetData("USER");

    // Check for pre-existing entry and delete if found
    if (NameDictionary.ContainsKey(z.ToString()) == true)
    {
        NameDictionary.Remove(z.ToString());
    }

    // add the username with this call context's unique ID
    NameDictionary.Add(z.ToString(), value);
}

Again, the CallContextString saved as the "USER" data is used as a key to reference the NameDictionary. And it's still not rocket science. All we have to do is ensure we're not adding the entry twice, by checking for it first.

Also, the VirtualSingleton class contains a method called DeleteContext. This method allows a context to remove itself from the singleton's dictionary. If this did not exist, then when a client closed, its data would remain in the singleton, which would slowly fill up and cause a memory leak.

Finally, the client assembly is a simple Form that references the VirtualSingleton as a remoting client. The clients assembly points to the VirtualSingleton as a well known type:

<configuration>
  <system.runtime.remoting>
    <application name="Client">

      <client url="HTTP://localhost:8043/Server">
        <wellknown type="VirtualSingleton, VirtualSingleton" 
        url="HTTP://localhost:8043/Server/VirtualSingleton.soap" />
      </client>

      <channels>
        <channel ref="http" />
      </channels>

    </application>
  </system.runtime.remoting>
</configuration>

In our simple client, when the form loads, it sets its CallContextString as the call context data "USER". For simplicity, a GUID is created for this purpose:

private void Form1_Load(object sender, System.EventArgs e)
{
    System.Guid g = System.Guid.NewGuid();
    CallContext.SetData("USER", new CallContextString(g.ToString()));
}

The important thing about this code is that it is done only once. From this point on, any calls to a VirtualSingleton don't need to reference the GUID we created.

When we set the user name in the singleton, we simply create a 'new' singleton to gain access to the original singleton and access the UserName property:

// Create a new singleton and set the username property.
VirtualSingleton.VirtualSingleton so = 
       new VirtualSingleton.VirtualSingleton();
so.UserName = this.textBox1.Text;

See? No extra parameters. No extra calls to set the context. It just works. Retrieving the user name is exactly the same:

// Create a new singleton and retrieve the username property
VirtualSingleton.VirtualSingleton so = new 
             VirtualSingleton.VirtualSingleton();
this.label2.Text = "User Code : " + so.UserName;

Finally, when the client closes, it removes its context from the singleton to allow the singleton to de-allocate any stored memory:

private void OnClosed(object sender, System.EventArgs e)
{
    VirtualSingleton.VirtualSingleton so = 
            new VirtualSingleton.VirtualSingleton();
    so.DeleteContext();
}

Comments welcome

If you know better than I do about any of this, comments are welcome. There are probably spelling and grammar mistakes in there somewhere (going on previous experience), and if you're bored then feel free to look for them.

Acknowledgements

I would like to thank my employer, John Boother at Autoscribe for allowing me to write this article based on research performed for my job.

History

  • 27th September 2003 -- Ooh, look! My first CodeProject article. The code even works!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dr Herbie
Team Leader
United Kingdom United Kingdom
Member
After ten years studying biology and evolution, I threw it all away and became a fully paid up professional programmer. Since 1990 I have done C/C++ Windows 3.1 API, Pascal, Win32, MFC, OOP, C#, etc, etc.
 
PS. In the picture, I'm the one in blue. On the right.
 

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalnice articlemembervasam12 Feb '04 - 19:42 
good
 
Sam
QuestionWhy don't you use a CAO?membersoci6 Oct '03 - 20:47 
The Remoting Client Activated Object does exactly what you want: store data per client. Ok, there are as many server objects as clients. But you got lifetime management free as oppused to yours. Hmm?
 
Zsolt Soczo
AnswerRe: Why don't you use a CAO?memberDr Herbie7 Oct '03 - 0:21 
Of course you are absolutely right you could use CAO, and for the majority of purposes you should use a CAO. I should probably have mentioned this in the article. However, it's always a good idea to have alternatives.
 
The immediate place where a Virtual Singleton stands out as the solution is where clients have both shared and non-shared data or resources. You might question the design -- it depends on whether you see that as two separate roles or not -- but given the design the Virtual Singleton is a useful implementation.
 
Lifetime management is great to have for free, but I'm prepared to implement my own if I get something extra in it's place.
 
Personally, I mostly see the Virtual Singleton as a 'tidy' interface to program with, just like CAO, but with the option of resource pooling between clients (I mostly work with database applications, so pooling and caching are always on my mind).
 
As I have said in another thread on this page, I don't use the Virtual Singleton for production code as it does still have problems for major 'enterprise' applications. If anyone comes up with a neat solution to lifetime management and failsafe persistence then write us all an articleSmile | :)
 

 
Dr Herbie @ Autoscribe
 
Remember, half the people out there have below average IQs.
GeneralState...memberThePhoenix30 Sep '03 - 7:15 
Nice article, and a very lucid explanation of the pattern. However, you haven't mentioned the fact that the virtual singleton is (by definition) stateful, and the potential problems that go along with that, such as server restarts (e.g. asp.net process recycling) causing state loss:Eek! | :eek: . There's also the problem of badly-behaved clients disconnecting without releasing themselves first. Both are of course quite surmountable, and this is a very good explanation of a useful construct.
GeneralRe: State...memberDr Herbie30 Sep '03 - 9:46 

ThePhoenix wrote:
virtual singleton is (by definition) stateful, and the potential problems that go along with that, such as server restarts (e.g. asp.net process recycling) causing state loss:
 

Absolutely right (although I have no knowledge of asp.net process recycling). I didn't mention this in the article (because I didn't want to put people off the idea before they had even thought about it) but I have currently disallowed the virtual singleton pattern in my current projects because I have yet to come up with an elegant solution for server fail-over. You could, of cource persist everything to a database or some form of in-memory storage. I did recently read about a java in-memory database that was failover safe, but I can't remember where -- it might have been a link from the codeproject lounge but I don't have time to look it up just yet. Maybe tomorrow at work where there's more bandwdth.
 

ThePhoenix wrote:
There's also the problem of badly-behaved clients disconnecting without releasing themselves first.
 
Yes. I did originally plan to use a client-side wrapper to the virtual singleton with a full singleton implementation. When you created the client singleton it would set the CallContext and when the Singleton went out of scope (when the program exited) it would delete itself from the virtual singleton. All calls to the local singleton would be passed off to the virtual singleton.
 
e.g.
 
 
class LocalSingleton
{
    private LocalSingleton mInstance = null;
    private LocalSingleton()
    {
        System.Guid g = System.Guid.NewGuid();
        CallContext.SetData("USER", new CallContextString(g.ToString()));    
    }
 
    public LocalSingleton Instance()
    {
        if (mInstance == null)
        {
            mInstance = new LocalSingleton();
        }
        return mInstance;
    }
 
    public ~LocalSingleton()
    {
        VirtualSingleton so = new VirtualSongleton();
        so.DeleteContext();
    }
 
    public string UserName
    {
        get
        {
            VirtualSingleton so = new VirtualSingleton();
            return so.UserName;
        }
        set
        {
            VirtualSingleton so = new VirtualSingleton();
            so.UserName = value;
        }
    }
}
 
 
I felt this would confuse the main design so I left it out. Maybe I'll add it at a later date as an addendum or maybe I'll just assume everyone will read this thread.
 

 
Glad you thought my explanation was clear. I decided to go for clarity over immediate usefulness Smile | :)
 
Dr Herbie @ Autoscribe
 
Remember, half the people out there have below average IQs.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 30 Sep 2003
Article Copyright 2003 by Dr Herbie
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid