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

Using Remote Object for Distributed COM+ Event Subscription.

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
8 Jan 20029 min read 98.9K   999   35   8
This article describes how to design, implement (C#) and configure the Custom Remoting Channel as a bridge to the COM+ Event Subscription.

Contents

Introduction
Usage
    Server config file
    Client config file
Concept and Design
    Bridge
Implementation
    RemoteEventSubscriberLib
    Virtual Subscriber Proxy
Test
    RemoteEventClass
    RemoteSubscriberObject
    HostServer
    TestClient
    TestPublisher
Conclusion

 

Introduction

Publishing of the application behaviour is a foundational issue of the event driven distributed architectures. In the COM+ services this feature has been introduced by the COM+ Event Model. This model represents a loosely coupled event system based on the Event knowledge stored in the COM+ catalog and using the Publisher/Subscriber design pattern. The COM+ Services are managed in .Net Framework using the System.EnterpriseServices.ServicedComponent class, where the Event Class is created as a managed object located in the COM+ catalog. The Publisher initiates the Event Class either locally or remotely using the remoting client proxy mechanism (note that the Event Class is derived from the MarshalByRefObject class). Firing an event from the managed code is straightforward and transparently to the unmanaged COM+ Event System. The situation on the other side, consumer of the events, is slightly different for distributed Subscribers specially using the Remoting object mechanism. This article describes how to make a bridge between the COM+ Event Subscription and remote object using the Custom Remoting Channel mechanism. This Bridge (LCE pseudo Channel) is created on the fly using the run-time compiling technique. Before than we will go to its implementation details, let's start it with its usage and configuration issues. I am assuming that you have a knowledge of the .Net Remoting and COM+ Event System.

Usage

Using the remote object for the distributed COM+ Event Subscriber requires the following:

  • the Remote object has to be derived from the Event Interface. This interface represents a contract between the Publisher, Subscriber and Event Class.
  • installing the RemoteEventClass assembly into the GAC (abstract definition of the Event Class and Interface).
  • registering the RemoteEventClass into the COM+ catalog using the utility regsvcs.exe
  • installing the RemoteEventSubscriberLib assembly into the GAC, which it is an implementation of the LCE pseudo channel.
  • configuring the LCE channel as a bridge between the Event Subscription and remoting object on the client side. Note that this bridge using the specified channel for remoting object such as http, tcp or custom channel.

Once we configured a bridge (programmatically or administratively), the COM+ Event system will forward the event calls to the remote object via the bridge using the remoting infrastructure. The loosely coupled event notification is using the fire&forget design pattern, that's why the remote event method can be attributed for OneWay. In this case the call is returned immediately back to the event system without waiting for its completion.

All pieces of the Distributed Event Subscriber are gluing using the configuration technique on the server and client hosted sides:

Server config file:

The server side requires a standard configuration of the wellknown remoting object (Singleton or SingleCall) and it might look like the following snippet:

<configuration>
 <system.runtime.remoting>
  <application name="RemoteEventSubscriber">
   <service>
    <wellknown mode="Singleton" type="RemoteSubscriberObject.Subscriber, RemoteSubscriberObject" 
               objectUri="Subscriber" />
   </service>
   <channels>
    <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" port="12345" />
    </channels>
  </application>
 </system.runtime.remoting>
</configuration>

Client config file:

The client side needs to add additional channel into the config file using the custom channel properties. The following properties are available:

Configuring the LCE Channel:

  • id specifies an unique name of the channel that can be used when referring to it (e.g. id="lce")
  • type is a full name of the channel that will be loaded (e.g. type="RKiss.RemoteEventSubscriberLib.RemoteEventSubscriber, RemoteEventSubscriberLib" means a RemoteEventSubscriber class in the assembly RemoteEventSubscriberLib), that's why assembly has to be installed into the GAC
  • ref is the channel template being referenced (e.g. ref="lce")
  • name specifies the name of the channel. Each channel has an unique name which is used for properly message routing (e.g. name="subscription"). Default name is lce.
  • priority specifies a preference for the channel. The default value is 1. Higher numbers mean higher priority. So the messages are queued based on this number.
  • objectType specifies a type of the Event Interface
  • objectUrl specifies an endpoint of the remoting object
  • eventClass specifies an Event class
  • methodName specifies a method name of the Event class, which is going to be fired
  • subscriptionName specifies a name of the subscription (option)
  • subscriptionDesc specifies a description of the subscription (option)
  • subscriptionId specifies a guid of the subscription (option). The default value is random Guid.
  • filterCriteria specifies a subscription filter criteria (option). The default value is null.
  • asyncCall specifies an asynchronously invoking the remote method call (option). Default value is false.

then the client config file might look like the following snippet:

<configuration>
 <system.runtime.remoting>
  <application >
   <client>
    <wellknown type="RemoteSubscriberObject.Subscriber, RemoteSubscriberObject" 
               url="tcp://localhost:12345/RemoteEventSubscriber/Subscriber" />
   </client>
   <channels>
    <channel type="RKiss.RemoteEventSubscriberLib.RemoteEventSubscriber, RemoteEventSubscriberLib"
             objectType="RKiss.RemoteEventClass.ILogMessage, RemoteEventClass" 
             objectUrl="tcp://localhost:12345/RemoteEventSubscriber/Subscriber" 
             subscriptionId="B35204DD-3D49-481d-852D-27D4887672F2" 
             eventClass="RKiss.RemoteEventClass.RemoteEventClass" methodName="Write2" filterCriteria="e='Hello World'" 
             asyncCall="true"/>
   </channels>
   </application>
 </system.runtime.remoting>
</configuration>

The above config bridge is creating the transient Event Subscription to fire a method Write2 when its argument has value Hello World on the remote object RemoteEventSubscriber using the tcp channel and port 12345.

Another example of the client config file shows how to config to bridges (subscription1 and subscription2):

<configuration>
 <system.runtime.remoting>
  <application >
   <client>
    <wellknown type="RemoteSubscriberObject.Subscriber, RemoteSubscriberObject" 
               url="tcp://localhost:12345/RemoteEventSubscriber/Subscriber" />
   </client>
   <channels>
    <channel ref="lce" name="subscription1" 
     eventClass="RKiss.RemoteEventClass.RemoteEventClass" methodName="Write" asyncCall="true"/>
    <channel ref="lce" name="subscription2" 
             subscriptionId="B35204DD-3D49-481d-852D-27D4887672F2"
             eventClass="RKiss.RemoteEventClass.RemoteEventClass" methodName="Write2" filterCriteria="e='Hello World'" /> 
    </channels>
  </application>
 <channels>
  <channel id="lce" type="RKiss.RemoteEventSubscriberLib.RemoteEventSubscriber, RemoteEventSubscriberLib" 
           objectType="RKiss.RemoteEventClass.ILogMessage, RemoteEventClass" 
           objectUrl="tcp://localhost:12345/RemoteEventSubscriber/Subscriber" 
           subscriptionName="RemoteEventSusbcriber" subscriptionDesc="This is a test" /> 
 </channels>
 </system.runtime.remoting>
</configuration>

 

Concept and Design

The concept of using the Remote object for Distributed Event Subscriber is based on creating a transient subscription for virtual subscriber proxy.  The virtual subscriber proxy represents a remote subscriber and its responsibility is to delegate events to the actually subscriber in either synchronously or asynchronously manner. In the following picture this concept is hidden in the Bridge module. The Bridge represents a dynamically glue between the Event System and Remoting infrastructure (transparent proxy of the remote object).

 

Concept of the LCE Custom Remoting Channel

 

The scenario of the Publisher/Subscriber notification is straightforward and full transparently. The Publisher fires an event method published in the COM+ Event System (tightly coupled system). Based on the registered subscription in the Event Store for this event class, the Event System invokes a specified event method on the transient subscription using the late binding design pattern. In this case, the transient subscription is represented by the Virtual Subscriber Proxy created and initiated on the fly based on the config custom channel properties. So, the virtual subscriber proxy is an actually client of the remote subscriber object using the standard remoting mechanism. Note that this concept solved the problem of direct using a transparent proxy of the remoting object as a transient subscriber.

Bridge

From the configuration viewpoint, the Bridge is represented by the LCE Channel. This channel is not a message transport channel, it is only way and mechanism how to administratively make a plumbing between the Event System and Remoting infrastructures. Its responsibilities are:

  • retrieving the custom channel properties
  • creating a proxy of the remoting object
  • dynamically creating an assembly of the Virtual Subscriber Proxy (remote object wrapper)
  • creating an instance of the Virtual Subscriber Proxy
  • creating LCE transient subscription and its registration into the COM+ Event Store

The key part of the Bridge is a dynamically creating assembly of the Virtual Subscriber Proxy. The technique (run-time compiling) what I am using in this solution can be used for another purpose also. It's generic mechanism and I would like to describe more details about that. The following flowchart shows its internals:

 

Image 2

 

The source code of the Virtual Subscriber Proxy is generated on the fly based on the config properties such as a name of the Event Method, method arguments, etc. Then the source code is compiled and its assembly is stored in the memory. From this assembly we can get a type of required class, which it is necessary for the Activator.CreateInstance. This process is done only one time during the Remoting Configuration. Note that assembly could be created using the Reflextion.Emit namespace classes, but the above technique is more convenient and readable.

 

Implementation

The LCE pseudo channel (Bridge) is implemented in the assembly RemoteEventSubscriberLib.dll using only references to the .Net namespaces. Metadata of the Event System unmanaged code such as IEventSystem, IEventSubscription and IEventSubscription2 have been created manually and inserted into the assembly. Let's look at in details about this module:

RemoteEventSubscriberLib

There is only one class - RemoteEventSubscriber, which is usually called by the Remoting Configuration with a collection of the custom channel properties, see the following code snippet:

// ctors
public RemoteEventSubscriber(){}
public RemoteEventSubscriber(IDictionary properties) : this( properties, null) {}
public RemoteEventSubscriber(IDictionary properties, IServerChannelSinkProvider serverSinkProvider)
{
   Connect(properties);
}

The class constructor (except default one) calling the method Connect to perform all functionality of the Bridge included its registration in the Event Store:

//connect Remote Object to the LCE System
public string Connect(IDictionary properties)
{
   bool asyncCall = false;
   string subscriptionName = "RemoteEventSusbcriber";
   string subscriptionDesc = "The Bridge between the Remote Object and COM+ Event System";
   string filterCriteria = null;

   // config values (xxx.exe.config file)
   // mandatory
   string[] objectType = properties["objectType"].ToString().Split(new char[]{','}, 2);
   string objectUrl = properties["objectUrl"].ToString();
   string eventClass = properties["eventClass"].ToString();
   string methodName = properties["methodName"].ToString();
   // option
   if(properties.Contains("name"))
      m_ChannelName = properties["name"].ToString();
   if(properties.Contains("asyncCall"))
      asyncCall = Convert.ToBoolean(properties["asyncCall"]);
   if(properties.Contains("subscriptionName"))
      subscriptionName = properties["subscriptionName"].ToString();
   if(properties.Contains("subscriptionDesc"))
      subscriptionDesc = properties["subscriptionDesc"].ToString();
   if(properties.Contains("subscriptionId"))
      m_subscriptionId = properties["subscriptionId"].ToString();
   if(properties.Contains("filterCriteria"))
      filterCriteria = properties["filterCriteria"].ToString();

   // create proxy of the Remote Object
   Assembly asm = Assembly.Load(objectType[1].TrimStart(new char[]{' '}));
   Type interfaceType = asm.GetType(objectType[0]);
   object robj = Activator.GetObject(interfaceType, objectUrl);
   MethodInfo mi = interfaceType.GetMethod(methodName);

   // Create type of the Remote Object Wrapper (ROW)
   Type trow = typeofROW("RKiss.RemoteObjectWrapper", "Wrapper", mi);

   // Create instance of the ROW
   object wpobj = Activator.CreateInstance(trow, new object[]{robj, mi, asyncCall});

   // create LCE subscription
   IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
   IEventSubcription2 sub = new EventSubcription() as IEventSubcription2;
   sub.SubscriptionName = subscriptionName;
   sub.Description = subscriptionDesc;
   sub.SubscriptionID = "{" + m_subscriptionId + "}";
   sub.Enabled = true;
   sub.EventClassID = "{" + Type.GetTypeFromProgID(eventClass).GUID.ToString() + "}";
   sub.MethodName = methodName;
   sub.FilterCriteria = filterCriteria;
   sub.SubscriberInterface = wpobj;
   es.Store("EventSystem.EventSubscription", sub); // registry into the Event System Store
   //
   return m_subscriptionId;
}

 

Virtual Subscriber Proxy

This is a key part of the implementation, it allows to dynamically create a remote event subscriber proxy for the transient subscription. The source code of the proxy is shown in the following text snippet:

/********************************************************************************************\ 
// dynamically created a source code of the Remote Object Wrapper
namespace ns
{
   using System;
   using System.Reflection;

   public class name       // class to delegate an COM+ Event to the Remote Object 
   { 
      // state
      object     m_obj;    // remote object proxy
      MethodInfo m_mi;     // Event method info
      bool       m_async;  // select async/sync mode, default is m_async=false
      delegate object delegateInvoke(object obj, object[] parameters); // delegate Invoke call
      // ctor
      public name(object obj, MethodInfo mi, bool async) 
      {
         m_obj = obj; m_mi = mi; m_async = async; 
      }
      // signature of the COM+ Event Method created on the fly 
      public void XYZ(...) 
      {
         // create an array of the method's arguments on the fly
         object[] args = new object[] {...}; 

         if(m_async==true) // invoking a Remote Object in the async manner
         {
            delegateInvoke di = new delegateInvoke(m_mi.Invoke);
            di.BeginInvoke(m_obj, args, null, null);
         }
         else
            m_mi.Invoke(m_obj, args); 
      }
   }
}
\******************************************************************************************/

as you can see, the above source code is a wrapper around the remote object proxy. The method XYZ(...) represents an event method. The name and its arguments are generated during the run-time using the System.Reflection namespace classes. When we have an image of the source code done, the rest of work is performing by CSharpCodeProvider class as it is shown in the following code snippet:

 // Create Assembly of the Remote Object Wrapper (ROW) based on the specified methodinfo
 private Type typeofROW(string ns, string name, MethodInfo mi) 
 {
    // source code generating 
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("namespace {0}", ns); // namespace
    sb.Append("{using System;using System.Reflection;"); 
    sb.AppendFormat("public class {0}", name); // class
    sb.Append("{object m_obj; MethodInfo m_mi; bool m_async;"); // members
    sb.Append("delegate object delegateInvoke(object obj, object[] parameters);"); // delegator
    sb.AppendFormat("public {0} (object obj, MethodInfo mi, bool async)", name); // ctor
    sb.Append("{m_obj=obj; m_mi=mi; m_async=async;}"); // ctor body
    sb.AppendFormat("public void {0}(", mi.Name); // method name
    foreach(ParameterInfo pi in mi.GetParameters()) // arguments
    {
       sb.AppendFormat("{0} {1} ,", pi.ParameterType.Name, pi.Name);
    }
    sb.Remove(sb.Length - 1, 1); // remove last comma
    sb.Append("){object[] args=new object[]{"); // create array of the method's arguments
    foreach(ParameterInfo pi in mi.GetParameters()) // argument's name
    {
       sb.AppendFormat("{0},", pi.Name);
    }
    sb.Remove(sb.Length - 1, 1); // remove last comma
    sb.Append("}; if(m_async==true){"); // check a calling mode
    sb.Append("delegateInvoke di=new delegateInvoke(m_mi.Invoke);"); // async call
    sb.Append("di.BeginInvoke(m_obj, args, null, null);}"); // fire&forget
    sb.Append("else m_mi.Invoke(m_obj, args);"); // sync call

    sb.Append("}}}"); // end of the method, class and namespace
    string srcWrapper = sb.ToString(); // source code

    // assembly compilation.
    CompilerParameters cp = new CompilerParameters();
    cp.ReferencedAssemblies.Add("System.dll");
    cp.GenerateExecutable = false;
    cp.GenerateInMemory = true; 
    cp.IncludeDebugInformation = false; 
    ICodeCompiler icc = new CSharpCodeProvider().CreateCompiler();
    CompilerResults cr = icc.CompileAssemblyFromSource(cp, srcWrapper);
    if(cr.Errors.Count > 0)
    {
       throw new Exception(string.Format("Build failed: {0} errors", cr.Errors.Count)); 
    }
    // return type of the ROW
    return cr.CompiledAssembly.GetType(ns + "." + name);
}

The Bridge can be disconnected from the COM+ Event System calling the following method:

// disconnect Remote Object from the LCE System
public void Disconnect()
{
   int errorIndex = 0;
   IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
   string strCriteria = "SubscriptionID=" + "{" + m_subscriptionId + "}";
   es.Remove("EventSystem.EventSubscription", strCriteria, out errorIndex);
}

 

Test

I created the following package of the assemblies to test the RemoteEventSubscriberLib solution:

  • RemoteEventClass.dll
  • RemoteSubscriberObject.dll
  • HostServer.exe
  • TestCient.exe
  • TestPublisher.exe

Note the above projects has only a test purpose, to show a usage of the LCE Channel and its evaluation. Their design and implementation has been simplified:

RemoteEventClass

This is an abstract definitions of the Event Class and Interface.

[assembly: ApplicationID("02385BFA-7A55-4704-9614-9BD15C7154CA")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(Value = false, Authentication = AuthenticationOption.None)]

namespace RKiss.RemoteEventClass
{

   [Guid("21EF4F1D-85E0-45f2-BFDC-858075327A2B")]
   public interface ILogMessage
   {
      [OneWay]
      void Write(object e);
      [OneWay]
      void Write2(DateTime dt, string e);
   }

   // meta class
   [Guid("E5A05525-3E0D-408d-9EEA-04827DEFA150")]
   [EventClass]
   [Transaction(TransactionOption.Disabled)]
   [ObjectPooling(Enabled=true, MinPoolSize=4, MaxPoolSize=60)]
   [EventTrackingEnabled]
   public class RemoteEventClass : ServicedComponent, ILogMessage
   {
      public void Write(object e) 
      {
         throw new Exception("This is a meta class");
      }
      public void Write2(DateTime dt, string e) 
      {
         throw new Exception("This is a meta class");
      }
   }
}

RemoteSubscriberObject

This is a Remote Subscriber object to receive events from the COM+ Event Subscription.

using RKiss.RemoteEventClass;

namespace RemoteSubscriberObject
{

   public class Subscriber : MarshalByRefObject, ILogMessage
   {
      public Subscriber()
      {
         Console.WriteLine("Subscriber is ready.");
      }

      [OneWay]
      public void Write(object e)
      {
         //Thread.Sleep(5000);
         Console.WriteLine(e);
      }
      [OneWay]
      public void Write2(DateTime dt, string e)
      {
         Console.WriteLine("{0}, {1}", dt, e);
      }
   }
}

HostServer

This is a server console program to host the remote subscriber object.

namespace HostServer
{
   public class Server 
   {
      public static void Main(string[] args)
      {
         try
         {
            RemotingConfiguration.Configure(@"..\..\HostServer.exe.config");
         }
         catch(Exception ex) 
         {
            Console.WriteLine(ex.Message);
         }
         //
         System.Console.Write("Hit <enter> to exit server...\n");
         System.Console.ReadLine();
      }
   }
}

TestClient

This is a client console program.

namespace TestClient
{
   class Client
   {
      static void Main(string[] args)
      {
         try
         {
            RemotingConfiguration.Configure(@"..\..\TestClient.exe.config");
         }
         catch(Exception ex) 
         {
            Console.WriteLine(ex.Message);
         }
         //
         System.Console.Write("Hit <enter> to exit client...\n");
         System.Console.ReadLine();
      }
   }
}

TestPublisher

This is a console program to fire an event.

using RKiss.RemoteEventClass;

namespace TestPublisher
{
   class Publisher
   {
      static void Main(string[] args)
      {
         int counter = 0;

         while(true) 
         {
            System.Console.Write("Hit <enter> to fire event\n");
            System.Console.ReadLine();
            try 
            {
               using(RemoteEventClass rec = new RemoteEventClass()) 
               {
                  rec.Write(string.Format("This is a test #{0}", counter++));
                  rec.Write2(DateTime.Now, "Hello World");
               }
            }
            catch(Exception ex) 
            {
               Console.WriteLine(ex.Message);
            }
         }
      }
   }
}
<p2>

 

Before starting the test the following assemblies have to be installed in the GAC:

  • RemoteEventClass on the both machine
  • RemoteEventSubscriberLib on the client machine
  • RemoteSubscriberObject on the server machine

and registering RemoteEventClass into the COM+ catalog on the client machine, see the following picture:

Image 3

 

After all the above checks, start the hostserver console program and then testclient program. To check the transient subscription in the COM+ Event Store use the Event Subscription Viewer from my article [1]. It's a very useful tool for that purpose (viewing transient subscriptions), because there is no other way to see it.

Image 4

In this moment we have our test environment ready. Launch the TestPublisher console program and press Enter key. Each time you pressed Enter, the Publisher will fire an event and the Subscriber will notify that message, see the following picture:

Image 5

Note that the remote object is hosted on the server as a singleton wellknown object, that's why we can see a prompt text Subscriber is ready from its ctor only one time at the beginning. (try to change mode to singlecall to see differences)

 

Conclusion

In this article has been described how the Remote object can be plumbed with the COM+ Event System as a transient subscription. Using the custom remoting channel feature this can be accomplished administratively, which it makes an easy deployment in the production environment.

 

[1] http://www.codeproject.com/useritems/subscriptionviewer.asp

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
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralDTC / Remoting Channel / X-Open DTP Pin
Roberto7813-Feb-04 2:56
Roberto7813-Feb-04 2:56 
GeneralRe: DTC / Remoting Channel / X-Open DTP Pin
nls13-Feb-04 5:23
nls13-Feb-04 5:23 
GeneralRe: DTC / Remoting Channel / X-Open DTP Pin
Roberto7813-Feb-04 8:35
Roberto7813-Feb-04 8:35 
GeneralException in RemoteConfiguration Pin
ghasempourh11-Feb-03 23:44
ghasempourh11-Feb-03 23:44 
GeneralRe: Exception in RemoteConfiguration Pin
Roman Kiss12-Feb-03 5:34
Roman Kiss12-Feb-03 5:34 
GeneralRe: Exception in RemoteConfiguration Pin
rupak_ju2-Jun-05 17:43
rupak_ju2-Jun-05 17:43 
GeneralRe: Exception in RemoteConfiguration Pin
cash200029-Jun-06 18:12
cash200029-Jun-06 18:12 
QuestionHow to avoid using host server? Pin
Yuriy22-Jan-03 20:52
Yuriy22-Jan-03 20:52 
Hello Roman,

Thank you for good, clear picture of using remoting in distributed COM+ event model.
I have one question regarding server side of your engine:
How we can avoid using host server for hosting remote subscriber object? In other words how can I use subscriber object as com+ dll?

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.