Using COM+ SPM in .NET

Introduction

Sharing transient state in a multi-user application deployed in COM+ has always been a problem. For example, let's say that an application component (Component A) is called and it set the values of some of its instance variables. If another component in the application wants to access this "state", there is no direct way to do it. A couple of solutions to this exist though. One solution is to stream the state of the object variables into a file and read this information in the other component. Although this is quite a workable solution, it has nothing to do with the business logic and is therefore extra code that someone has to write and maintain. The COM+ Shared Property Manager (SPM) provides just the solution that we need by allowing applications to use a standard API to create and share properties.

This article looks at how you can use the COM+ Shared Property Manager (SPM) in .NET to implement the sharing of state across components. I'm assuming that you are familiar with VB.NET, Windows Forms, Serviced Components and COM+. Before we actually get down to see the solutions, let's get aside some basics first.

What is Shared Property Manager

In COM+, shared transient state for objects is managed by using the Shared Property Manager (SPM). The SPM is a resource dispenser that you can use to share state among multiple objects within a server process.

You cannot use global variables in a distributed environment because of concurrency and name collision issues. The Shared Property Manager eliminates name collisions by providing shared property groups, which establish unique namespaces for the shared properties they contain. The SPM also implements locks and semaphores to protect shared properties from simultaneous access, which could result in lost updates and leave properties in an unpredictable state.

Shared properties stored in the SPM are available only to objects running in the same process. This means that the objects that will use the SPM for storing values and that will need to have access to these values must be installed as part of the same COM+ application. It is possible for system administrators to move COM+ classes from one package to another after your COM+ application has been deployed. If you rely on several objects sharing properties through the SPM, you should clearly document that they must be installed in the same COM+ application.

It's also important for components sharing properties to have the same activation attribute. If two components in the same package have different activation attributes, they generally won't be able to share properties. For example, if one component is configured to run in a client process and the other is configured to run in a server process, their objects will usually run in different processes even though they're in the same package.

Shared transient state is state information kept in memory that does not survive system failures. The information is designed to be shared by multiple objects across transaction (but not across process) boundaries.

Shared Property Manager API

In COM+, shared properties are organized into groups. These groups provide a sort of "namespace" that organizes all the properties for a process. In addition, groups help reduce the number of possible naming conflicts among properties. For instance, a group cannot have two properties with the same name, but two different groups can have properties with the same name. The API for COM+ SPM then, is organized around three main objects as shown in the following diagram.



The following table explains each of the above objects.

Object Name Description
SharedPropertyGroupManager This object allows you to create a group manager that helps you manage all the property groups for an application. This is the only object that can be directly instantiated.
SharedPropertyGroup  This object allows you to create a property group which can then hold a set of properties. To create this object you first need to create a group manager.
SharedProperty  This object allows you to create the properties that your application needs to share between the various components in an application.

The following sections explain each of these objects in more detail and take a look at the various methods.

The SharedPropertyGroupManager Object

The SharedPropertyGroupManager is the only object that you will directly create. This object allows you to create property groups and has a constructor that takes no arguments. If you were to write code for this, the code would look like the following:

Imports System
Imports System.EnterpriseServices
Public Class myClass : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Public Sub someMethod()
spmGroupManager = New SharedPropertyGroupManager
End
Sub
End
Class

The following table shows some of the core methods of this object.

Method Name Description
CreatePropertyGroup

This method finds or creates a property group with the given information. This method takes the following parameters:

  1. Group Name. This argument indicates the name of the property group that you want to create.
  2. Lock Mode. This argument indicates how accesses to the properties in this group are locked. The possible values are Method and SetGet. Method indicates that accesses to the properties in this group are locked until the method that is using those exits. This means that other objects that want to access the same properties have to wait for the method to get over. SetGet indicates that accesses to the properties are locked for the duration of the access only.
  3. Release Mode. This argument indicates when the properties for this group are released from memory. The possible values are Process and Standard. Process indicates that the memory this group occupies is freed only when the hosting process terminates. Standard frees the memory when the last client releases a reference to the group.
  4. Exists Flag. This argument indicates whether the group asked for already exists.
Group This method requests for a group that already exists. You should use this method only if you know that the group already exists. It is better to encapsulate this method inside a Try...Catch block.

The SharedPropertyGroup Object

Once you have created a shared property group manager object, your next step would be to create a property group that can be used to store properties. To create a property group, you use the SharedPropertyGroupManager object to create a group and store its reference into an object of type SharedPropertyGroup. The following code snippet shows how a group can be created.

Imports System
Imports System.EnterpriseServices
Public Class myClass : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Private spmGroup As SharedPropertyGroup
Private bExists As
Boolean
Public Sub someMethod()
spmGroupManager = New SharedPropertyGroupManager
spmGroup = spmGroupManager.CreatePropertyGroup("MyGroup", _
PropertyLockMode.Method, PropertyReleaseMode.Process, bExists)
End
Sub
End
Class

The following table illustrates the core methods of the SharedPropertyGroup class.

Method Name Description
CreateProperty

This method creates a property with the given name and takes the following arguments.

  1. Name. This argument indicates the name of the property to create.
  2. Exists Flag. This flag indicates whether a property with the same name already exists.
    The output of this method call is a reference to an object of type SharedProperty.
CreatePropertyByPosition

This method creates a property based on position rather than by name and takes the following arguments.

  1. Position. This argument indicates the position where the property is to be created and replaces the name of the property. The position can be any valid integer value, including positive and negative numbers.
  2. Exists Flag. This flag indicates whether a property with the same position already exists.

Note that properties created by position cannot be retrieved by a method that looks at name. For example, if you created a property by using CreateProperty, you cannot access it by using the CreatePropertyByPosition.

Property This method returns the property with the given name and takes as argument the name of the property.
PropertyByPosition This method returns the property at the given position and takes as argument the position for the property.

The SharedProperty Object

Once you have created a shared property group object, your next step would be to create the properties. To create a property, you use the SharedProperty object to create a property and use its methods to store and retrieve values from it. The following code snippet shows how a property can be created.

Imports System
Imports System.EnterpriseServices
Public Class myClass : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Private spmGroup As SharedPropertyGroup
Private spmProperty As SharedProperty
Private bExists As Boolean
Public Sub someMethod()
spmGroupManager =
New SharedPropertyGroupManager
spmGroup = spmGroupManager.CreatePropertyGroup("MyGroup", _
PropertyLockMode.Method, PropertyReleaseMode.Process, bExists)
spmProperty = spmGroup.CreateProperty("ConnString", bExists)
spmProperty.Value = "(a sample connection string)"
End Sub
End
Class

The only property of a shared property is the value, which gives the current value stored in the property.

Ok, now that we have looked at the core basics, we have enough knowledge to construct a useful application that demonstrates the usage of shared properties. I have written a windows forms application and a couple of classes that can be deployed in COM+ to illustrate all these concepts. The following sections outline the sample application.

Sample Application

The sample application discussed here will show you how to use the SPM in COM+ from .NET. The overall architecture of the sample is as follows:

We create a class that contains the basic methods to create the SPM and populate it with a property and its value.

We then create two clients that provide methods to get the property value and to also set it to a different value. Along the way, we will see how clients are locked between property accesses.

Note that not all areas of COM+ SPM are covered. For coverage of all the areas and the possible methods, you can refer to the MSDN library. Based on the above explanation, here is a diagrammatic representation of how the application will work.



Fire-up Visual Studio .NET and follow the steps outlined below.

- Create a class library project and name it ComSpm as shown in the following figure.



- When you click on [Ok], VS.NET creates a solution of the same name and a class library.

- Open the VS.NET command line prompt and type in the following command: sn -k sct.snk

- Right-click the references node and add a reference to System.EnterpriseServices. Also rename the class file to MainComponent.vb.

- Paste the following code into the code editor.

Imports
System
Imports System.EnterpriseServices
' This class is the main component that will create the shared property
' groups and the associated properties
<EventTrackingEnabled(True)> _
Public Class MainComponent : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Private spmGroup As SharedPropertyGroup
Private spmProperty As SharedProperty
Private bExists As Boolean
Public Sub New()
' Create the shared property group manager. It is by this object
' that we can access all the other objects
spmGroupManager = New SharedPropertyGroupManager
' Create a property group and give it a name. The property group will
' lock at the method level and will be released when the process ends
spmGroup = spmGroupManager.CreatePropertyGroup("MyGroup", _
PropertyLockMode.Method, PropertyReleaseMode.Process, bExists)
' Create a property and assign a value to it
spmProperty = spmGroup.CreateProperty("ConnString", bExists)
spmProperty.Value = "(a sample connection string)"
End Sub
End
Class

- Open the AssemblyInfo.vb file
and paste the following code.

Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.EnterpriseServices
' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
' Review the values of the assembly attributes
<Assembly: AssemblyTitle("")>
<
Assembly: AssemblyDescription("")>
<
Assembly: AssemblyCompany("")>
<
Assembly: AssemblyProduct("")>
<
Assembly: AssemblyCopyright("")>
<
Assembly: AssemblyTrademark("")>
<Assembly: CLSCompliant(True)>
<
Assembly: ApplicationName("SriSamp")>
<
Assembly: ApplicationActivation(ActivationOption.Server)>
<
Assembly: AssemblyKeyFile("C:\Sct.snk")>
'The following GUID is for the ID of the typelib if this project is exposed to COM
<Assembly: Guid("F3C200B5-341D-4A56-AF46-1945E01F985D")>
' Version information for an assembly consists of the following four values:
'
' Major Version
' Minor Version
' Build Number
' Revision
'
' You can specify all the values or you can default the Build and Revision Numbers
' by using the '*' as shown below:
<Assembly: AssemblyVersion("1.0.0.1")>

Note that I have specified the COM+ application name to be SriSamp and the application type to be Server. The assembly will be signed using the key generated in the earlier step.

- Right click on the solution and add a new class library project and name it ClientA. You can instruct VS.NET to place this project within the ComSpm project.

- Add a reference to System.EnterpriseServices and also rename the class file to ClientA.vb.

- Paste the following code into the code editor.

Imports System
Imports System.EnterpriseServices
Imports System.Threading
' This class is a client component that will query the property and
' get its value
<EventTrackingEnabled(True)> _
Public Class ClientA : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Public Function GetPropertyValue() As String
Dim strValue As String
' Create a property manager object and access the property
spmGroupManager = New SharedPropertyGroupManager
Try
strValue = spmGroupManager.Group("MyGroup").Property("ConnString").Value
Catch ex As Exception
strValue = "(property does not exist)"
End Try
Return (strValue)
End Function
Public Sub SetPropertyValue(ByVal strValue As String)
' Create a property manager and access the property
spmGroupManager = New SharedPropertyGroupManager
Try
spmGroupManager.Group("MyGroup").Property("ConnString").Value = "(property value set from ClientA) = " + strValue
Catch ex As Exception
strValue = "(could not set property value from ClientA)"
End Try
Thread.Sleep(3000)
End Sub
End
Class

Note that we have specified a Thread.Sleep(3000) in the SetPropertyValue method. This is to force the method to wait so that we can demonstrate the act of locking of shared properties. Also, note that we try to access the ConnString property directly in the method and catch exceptions if any.

- Open the AssemblyInfo.vb file and paste the following code. 

Imports
System
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.EnterpriseServices
' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
' Review the values of the assembly attributes
<Assembly: AssemblyTitle("")>
<
Assembly: AssemblyDescription("")>
<
Assembly: AssemblyCompany("")>
<
Assembly: AssemblyProduct("")>
<
Assembly: AssemblyCopyright("")>
<
Assembly: AssemblyTrademark("")>
<
Assembly: CLSCompliant(True)>
<
Assembly: ApplicationName("SriSamp")>
<
Assembly: ApplicationActivation(ActivationOption.Server)>
<
Assembly: AssemblyKeyFile("C:\Sct.snk")>
'The following GUID is for the ID of the typelib if this project is exposed to COM
<Assembly: Guid("3E17D6A5-F98F-4FD4-8B61-B276EFE05B8E")>
' Version information for an assembly consists of the following four values:
'
' Major Version
' Minor Version
' Build Number
' Revision
'
' You can specify all the values or you can default the Build and Revision Numbers
' by using the '*' as shown below:
<Assembly: AssemblyVersion("1.0.0.1")>

This file is very similar to the one that we placed for the MainComponent class.

- Right click on the solution and add a new class library project and name it ClientB. You can instruct VS.NET to place this project within the ComSpm project.

- Add a reference to System.EnterpriseServices and also rename the class file to ClientB.vb.

- Paste the following code into the code editor.

Imports System
Imports System.EnterpriseServices
' This class is a client component that will query the property and
' get its value
<EventTrackingEnabled(True)> _
Public Class ClientB : Inherits ServicedComponent
Private spmGroupManager As SharedPropertyGroupManager
Public Function GetPropertyValue() As String
Dim strValue As String
' Create a property manager object and access the property
spmGroupManager = New SharedPropertyGroupManager
Try
strValue = spmGroupManager.Group("MyGroup").Property("ConnString").Value
Catch ex As Exception
strValue = "(property does not exist)"
End Try
Return (strValue)
End Function
Public Sub SetPropertyValue(ByVal strValue As String)
' Create a property manager and access the property
spmGroupManager = New SharedPropertyGroupManager
Try
spmGroupManager.Group("MyGroup").Property("ConnString").Value = "(property value set from ClientB) = " + strValue
Catch ex As Exception
strValue = "(could not set property value from ClientB)"
End Try
End Sub
End
Class

- Open the AssemblyInfo.vb file and paste the following code.

Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.EnterpriseServices
' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
' Review the values of the assembly attributes
<Assembly: AssemblyTitle("")>
<
Assembly: AssemblyDescription("")>
<
Assembly: AssemblyCompany("")>
<
Assembly: AssemblyProduct("")>
<
Assembly: AssemblyCopyright("")>
<
Assembly: AssemblyTrademark("")>
<
Assembly: CLSCompliant(True)>
<
Assembly: ApplicationName("SriSamp")>
<
Assembly: ApplicationActivation(ActivationOption.Server)>
<
Assembly: AssemblyKeyFile("C:\Sct.snk")>
'The following GUID is for the ID of the typelib if this project is exposed to COM
<Assembly: Guid("0E02595E-4A0F-4879-B5C0-0438DB11CA1B")>
' Version information for an assembly consists of the following four values:
'
' Major Version
' Minor Version
' Build Number
' Revision
'
' You can specify all the values or you can default the Build and Revision Numbers
' by using the '*' as shown below:
<Assembly: AssemblyVersion("1.0.0.1")>

- You will now need to build all these three class libraries. You can do this by right-clicking each project and selecting Build from the context menu that appears.

- After you have built the projects successfully, you need to register these with COM+ so that they appear in the COM+ explorer. To do this, I wrote a simple batch file that has the following contents.

regsvcs .\bin\ComSpm.dll
regsvcs .\ClientA\bin\ClientA.dll
regsvcs .\ClientB\bin\ClientB.dll

You need to then save this batch file (let's call it register.bat) in the same directory as of the application. When you run this batch file, all the three components are registered with COM+ and appear in the MMC snap-in as shown.



Note that I'm running COM+ 1.5 in Windows XP, thus you might not see all the nodes displayed here (like legacy components) if you are using an earlier version of COM+.

- Now right-click the COM+ application, select Properties. In the dialog box that opens, choose the Security tab and clear the Enforce Access Checks For This Application checkbox as shown.



- Right click on the solution and add a new windows forms project and call it TestApp. I designed the following form (called frmTestSpm) with the following naming conventions (all other values were left as their defaults).



- Add a reference to System.EnterpriseServices, ClientA, ClientB for this form.

- Open the code editor for this form and paste the following code in it. Note that the forms designer would have created some code. Do not overwrite it.

Public Class frmTestSpm
Inherits System.Windows.Forms.Form
Private Sub cmdCreateSpm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCreateSpm.Click
' Create the SPM main component so that the SPM is initialized
' with some property and value
Dim oComSpm As ComSpm.MainComponent
oComSpm =
New ComSpm.MainComponent
oComSpm.Dispose()
MsgBox("COM+ SPM has been initialized.", MsgBoxStyle.OKOnly, "COM+ SPM Test")
End Sub
Private Sub cmdClientAGet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdClientAGet.Click
' Create and call the ClientA object's methods. This component assumes
' that the SPM has some property based on which a result is returned
Dim oClientA As ClientA.ClientA
Dim strValue As String
Try
oClientA = New ClientA.ClientA
txtClientAOutput.Text = "Client A has been initialized..." + vbCrLf
strValue = oClientA.GetPropertyValue()
txtClientAOutput.Text = txtClientAOutput.Text + "Property Value = " + strValue + vbCrLf
Catch ex As Exception
txtClientAOutput.Text = ex.Message
End Try
oClientA.Dispose()
End Sub
Private Sub cmdClientBGet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdClientBGet.Click
' Create and call the ClientB object's methods. This component assumes
' that the SPM has some property based on which a result is returned
Dim oClientB As ClientB.ClientB
Dim strValue As String
Try
oClientB = New ClientB.ClientB
txtClientBOutput.Text = "Client B has been initialized..." + vbCrLf
strValue = oClientB.GetPropertyValue()
txtClientBOutput.Text = txtClientBOutput.Text + "Property Value = " + strValue + vbCrLf
Catch ex As Exception
txtClientBOutput.Text = ex.Message
End Try
oClientB.Dispose()
End Sub
Private Sub cmdClientASet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdClientASet.Click
' Create the ClientA object and call the method that sets a value. After setting the
' value sleep for 3 seconds
Dim oClientA As ClientA.ClientA
Try
oClientA = New ClientA.ClientA
oClientA.SetPropertyValue("CLIENTA")
txtClientAOutput.Text = txtClientAOutput.Text + oClientA.GetPropertyValue() + vbCrLf
Catch ex As Exception
txtClientBOutput.Text = ex.Message
End Try
oClientA.Dispose()
End Sub
End
Class

What this code essentially does is to instantiate the various objects in COM+ and then calls the appropriate methods in them. The output from each method is then aggregated and displayed in the appropriate text boxes.

- Now you are all done! Your solution explorer should now look like this.



Make TestApp as the startup project and press F5 to run the application and the following screen appears.



You can play around with this application and click the various buttons to see how the SPM works and the properties are retrieved.

If you run two instances of this application and press [Client A (Set)] in one instance and the [Client A (Get)] in the other instance parallely, you will see that the second instance will wait for 3 seconds and then get the property. Remember the sleep that we put? That's causing this client to wait...

Make sure that you click the [Create SPM] button first; otherwise you will see exception messages displayed in the text boxes.

That brings us to the end of this article. We have seen COM+ SPM working for us and how it can be used to share properties between components running in the same COM+ application. You can experiment with the code given and try out various other possibilities like using positional properties etc. Hopefully, I have shown you the power that the SPM can give to your applications and I leave it to you readers to experiment...

Conclusion

In this article, we have seen how COM+ SPM works. In future articles we will see some more interesting features of COM+. You can also look at the MSDN India site for a set of articles on the various COM+ features.

For any suggestions or questions, you can mail me at [email protected].