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.
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
{
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
{
CallContextString z = (CallContextString) CallContext.GetData("USER");
if (NameDictionary.ContainsKey(z.ToString()) == true)
{
NameDictionary.Remove(z.ToString());
}
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:
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:
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!
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.