Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Virtual Singleton Pattern in C#

0.00/5 (No votes)
29 Sep 2003 1  
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:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here