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

A Very Simple Persistent Cache in a COM+ Component

Rate me:
Please Sign up or sign in to vote.
3.64/5 (18 votes)
18 Apr 20049 min read 68K   830   30   5
This article describes how to use a COM+ component as a persistent cache. From this simple base, a complete caching system could be built.

Imutome.COMPlusCache

Introduction

Well, here goes with my first Code Project article in which I describe how to create a COM+ component that will act as a persistent cache for data (in this case, just a string). Once the COM+ cache component is running, it will persist its data until its host machine is turned off or the component is stopped. Come back in a few days time and the data will still be there! Log off and log back on as someone else; the data will still be there!

I'm sure there are many uses to which this could be put or ways in which the concept could be extended. It's probably a commonly used approach that everyone knows about apart from me!

Background

Why did I develop this small example? Well, I wanted to create a caching mechanism for a multi-tier application. The idea was that a cache component, similar to the one described here, could run on each front-end server (of which there might be several), caching infrequently changing data. Any .NET application on one of the servers could talk to the cache component on the same server to obtain the cached data. The cache components themselves, would obtain their data via .NET Remoting, from a central Remotable component (this would provable live on, or close to, a database server and could even be a caching component itself). A cache invalidation and refresh mechanism could be built using Remoting Callbacks. Once I implement this, I may describe it in a future article.

For the moment, I shall just describe the simple cache mechanism. I hope you find it useful.

Environment

I developed this example using Visual Studio 2003 on Windows 2000 with .NET Framework 1.1. I have not been able to try it with the 1.0 Framework, although I think it should be possible to make it work if certain 1.1-only attributes are removed and more configuration is done via the Component Services administration tool.

The Code

The code presented here is exceptionally simple. However, what I found interesting was the way in which attributes need to be used, and the tasks that have to be performed in the Windows 2000 Component Services administrative tool.

The example code includes two projects:

  • Cache - a COM+ component library (.dll).
  • CacheClient - a console application (.exe).

The Cache COM+ Component

Creating a COM+ component library is easy. The steps are as follows:

  • Using Visual Studio .NET, create a new blank solution (mine is called COMPlusCache).
  • To this new solution, add a new C# project of type 'Class Library' (I named it Cache).
  • Rename class1.cs to Cache.cs.
  • Inside Cache.cs pick an appropriate namespace and rename the class (I used the namespace Imutome.COMPlusCache and just called the class Cache).
  • To make Cache a COM+ component, you have to extend System.EnterpriseServices.ServicedComponent. To do this, first add a reference to System.EnterpriseServices (right click on the References item in the project, select 'Add Reference' and scroll down and select System.EnterpriseServices).
  • Then, in the cache.cs file, add using System.EnterpriseServices; at the top and make your Cache class extend ServicedComponent.

You now have a basic COM+ component which should compile but, of course, will do nothing. At this point, your code should look like this:

C#
using System;
using System.EnterpriseServices;

namespace Cache
{
   /// <summary>
   /// Summary description for Class1.
   /// <summary>
   public class Cache : ServicedComponent
   {
      public Cache()
      {
         //
         // TODO: Add constructor logic here
         //
      }
   }
}

Now, to make it do something, to the class, add a private string and a few methods, one to retrieve the string and one to set it:

C#
public class Cache : ServicedComponent
{
   private string _data;

   ...

   public string GetData()
   {
      return _data;
   }

   public void SetData(string str)
   {
      _data = str;
   }
}

When I got to this point, I thought, rather naively, that all I needed to do was compile and then install the resulting assembly as a Component Service. I tried to install the class by opening the .NET Command Prompt, navigating to the Cache project's bin/Debug directory, and running the command regsvcs Cache.dll. Sadly, I got this message:

D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug>regsvcs Cache.dll
Microsoft (R) .NET Framework Services Installation Utility Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002.  All rights reserved.


The following installation error occurred:
1: The assembly 'D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug\Cache.dll' does not
 have a strong name.

D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug>

So, if you want to install code as a COM+ service, you have to make sure that it has a strong name. What this means is that the code has to be signed with a cryptographic key. This, fortunately, is easy to do:

  • In your .NET command window, navigate to the root directory of the solution.
  • Run the command sn -k <keyname>. This will create a key file called <keyname>.snk which contains a pair of cryptographic keys.
  • Open the AssemblyInfo.cs file, and at the bottom, insert the full path to the key against the AssemblyKeyFile entry.

The bottom of the AssemblyInfo.cs file should look similar to this:

C#
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile(@"d:\Temp\dotnet\COMPlusCache\Imutome.COMPlusCache.snk")]
[assembly: AssemblyKeyName("")]

Now, if you repeat the attempt to install the COM+ component using regsvcs, it will work, although a warning message will be displayed:

D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug>regsvcs Cache.dll
Microsoft (R) .NET Framework Services Installation Utility Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002.  All rights reserved.

WARNING: The assembly does not declare an ApplicationAccessControl Attribute.  A
pplication security will be enabled by default.
Installed Assembly:
        Assembly: D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug\Cache.dll
        Application: Cache
        TypeLib: D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug\Cache.tlb

D:\Temp\dotnet\COMPlusCache\Cache\bin\Debug>

What on earth is a ApplicationAccessControl Attribute? Don't worry, you'll read about this later on. Well, at least the component is installed and, if you open Component Services (Start -> Settings -> Control Panel -> Administrative Tools -> Component Services) and navigate down through the tree to COM+ Applications, you will see your newly installed application:

Image 2

Congratulations, you have installed a COM+ Application. Now, I thought, surely, I just need to create a client application which talks to this component, and I'll see it caching data (Of course, as many of you will know, there is more work necessary before this will work!).

The Client Application

To create the client application, add a new C# project, named 'CacheClient', of type 'Console Application' to the overall solution. Then, ensure that both the Cache project and System.EnterpriseServices are registered as references. Finally, add a main() method to the class which allows you to either read or replace the string stored in the Cache component. The entirety of the Class1.cs file is this:

C#
using System;
using Imutome.COMPlusCache;

namespace Imutome.COMPlusCache
{
   class Class1
   {
      [STAThread]
      static void Main(string[] args)
      {
         //
         // TODO: Add code to start application here
         //
         Cache c = new Cache();
         string s = c.GetData();
         if(s != null)
            Console.WriteLine("Cached string is " + s);

         if(args.Length > 0 && args[0].Length > 0)
         {
            c.SetData(args[0]);
            Console.WriteLine("Cached string reset to " + args[0]);
         }
      }
   }
}

In the main method, a new Cache object is created. I'm not quite sure what is really happening, but I think that the COM+ application starts running, if it is not running already, and a Cache COM+ object is created inside it. Then, it is a proxy object (or a stub in older parlance) that is created inside the client application. The proxy object deals with communication with the COM+ object and, from the perspective of the client program, it is just as if the Cache object is inside the local code.

Then, GetData() is called on the Cache object. If this has a value, a message is written to the console. Following on from this, if there is a command line argument, arg[0], this is passed to SetData() of the Cache object and a message is printed saying that the cached string has been changed.

So, if you compile CacheClient and run it (I tend to open a command prompt in CacheClient/bin/Debug) ......

...... it won't work!! In fact, whenever you run it, the value stored by the Cache COM+ component is null. To solve this problem, you need to add a little bit of code and configure a few more settings.

Pooling

We want to make a cache by creating one instance of the Cache class which will then stay alive for a long time. To do this, we can make use of the Component Services object pooling system.

Object pooling is intended as a performance improvement tool. Normally, every time an object needs to be used, it must first be instantiated. This instantiation has a processing cost, as does the destruction of the object after it has been finished with. However, if a number of objects are created and placed in a pool, they can be retrieved for use when required and then placed back in the pool when finished with. Pooling can be more efficient, as the cost of retrieving and replacing objects is lower than the cost of creating and destroying them.

The trick we are going to use here is that of creating a pool that only ever has one object. This object will exist for a long time; and it will cache our data.

COM+ components always get asked whether there can be pooling. If they say no, pooling doesn't work! To allow the Cache component to be pooled, add this code to the class:

C#
protected override bool CanBePooled()
{
   return true;
}

Configuring COM+ Attributes

If, through the Component Services administrative tool, you look at the properties of COM+ Applications and the properties of the COM+ components within these applications, you will see that there are a number of attributes that can be configured. Some of these attributes can only be configured through the properties windows, whilst others can be configured in code.

In the cache.cs file, add the following before the class definition of the cache object:

C#
[ObjectPooling(Enabled=true, MinPoolSize=1, MaxPoolSize=1, CreationTimeout=20000)]
[ComponentAccessControl(false)]
public class Cache : ServicedComponent
...

These entries do the following:

  • ObjectPooling - this sets object pooling to be enabled and sets the size of the pool to always be one object (maximum 1, minimum 1; same thing really!). The creation timeout is irrelevant.
  • ComponentAccessControl - setting this to false, disables the use of access controls for the component.

At this point, delete the old version of the COM+ Application through the Component Services administration tool, recompile the solution, and reinstall the cache application using regsvcs.

Now, once the Cache COM+ application is running, there will always be one, and only one Cache object, which will persist for the lifetime of the application. If you run cacheClient, you will find that the string will persist between executions. However, if you leave a gap of more than a few minutes between using cacheClient, the string will disappear. This is because a COM+ application turns itself off if left idle for more than a few minutes. To solve this problem, you have to configure the COM+ application: in Component Services, locate the Cache application, and, on the Advanced properties tab, in 'Server Process Shutdown', select 'Leave Running when Idle':

Image 3

This will get the cache to persist while you are logged in. However, two more settings need to be altered to make the cache persist even if you log out:

  • On the Identity tab, set the application to run as a named user. I just used the administrator's user ID and password, although I imagine that a user with lesser permissions might be OK.
  • On the Activation tab, set the 'Activation Type' to be 'Server' so that the application runs in its own server process, rather than yours which will terminate when you log out.

Now, if you run cacheClient to install a new string in the cache, you should find that it will persist for a very long time and that you can even logout and back in as another user and still see that the string is being cached.

Conclusion

It is easy to create a component that, once it has been started, caches data until the machine is turned off. Whilst this is a very simple example, it would be easy to extend it so that, for example, the cache could be a hashtable containing a whole range of different types of objects. I expect that there are many other applications for this approach, my favorite being to use it along with .NET Remoting, as the basis for a distributed caching architecture.

The key to getting a cache to work is to configure the COM+ application and the components correctly. Some of this can be done in code, and the rest of the configuration can be done via the Component Services administrative tool.

History

Version 1 - 13th April 2004.

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


Written By
Web Developer
United Kingdom United Kingdom
Matt lives in Bristol, United Kingdom and runs Imutome Ltd, a software development and IT Services company specialising in Microsoft .NET and related technologies.

Comments and Discussions

 
Generalvery good thank you Pin
evan y sh11-Oct-10 17:27
evan y sh11-Oct-10 17:27 
GeneralMy vote of 4 Pin
evan y sh11-Oct-10 16:38
evan y sh11-Oct-10 16:38 
GeneralCacheClient should use "Cache.Dispose()" Pin
Thorsten_H23-Nov-08 5:54
Thorsten_H23-Nov-08 5:54 
GeneralOverly Complicated Pin
is0butane17-May-05 9:29
is0butane17-May-05 9:29 
GeneralIncorrect use of Pooling Pin
caractacus15-Feb-05 4:16
caractacus15-Feb-05 4:16 

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.