Click here to Skip to main content
15,901,205 members
Articles / Programming Languages / C#
Article

The Virtual Singleton Pattern in C#

Rate me:
Please Sign up or sign in to vote.
4.00/5 (7 votes)
29 Sep 2003CPOL5 min read 72.3K   150   36   5
A simple implementation of the Virtual Singleton using the CallContext and remoting in C#.

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:

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

On the server:

C#
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:

C#
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:

XML
<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:

C#
[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:

C#
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):

C#
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:

XML
<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:

C#
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:

C#
// 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:

C#
// 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:

C#
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)


Written By
Team Leader
United Kingdom United Kingdom
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.

Comments and Discussions

 
Generalnice article Pin
vasam12-Feb-04 19:42
vasam12-Feb-04 19:42 
QuestionWhy don't you use a CAO? Pin
soci6-Oct-03 20:47
soci6-Oct-03 20:47 
AnswerRe: Why don't you use a CAO? Pin
Dr Herbie7-Oct-03 0:21
Dr Herbie7-Oct-03 0:21 
GeneralState... Pin
ThePhoenix30-Sep-03 7:15
ThePhoenix30-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... Pin
Dr Herbie30-Sep-03 9:46
Dr Herbie30-Sep-03 9:46 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.