Click here to Skip to main content
Click here to Skip to main content
Go to top

Using the COM+ Event System service in the .Net Application.

, 6 Sep 2001
Rate this:
Please Sign up or sign in to vote.
This article describes how the COM+ LCE can be incorporated in the .Net application using the C# language.
<!-- Add the rest of your HTML here -->

Introduction

The modern architecture model requires publishing its business process behavior in the loosely coupled design pattern. The COM+ Event System can accomplish this task with a minimum coding. This article describes how the LCE can be incorporated in the .Net application using the C# language. In my sample I used a simple Log Message publisher to notify a subscriber in the ‘fire & forgot’ manner. Based on that, it can be built a more sophisticated publisher/subscriber for post-processing purposes such as analyzing, modeling, tuning, animation, test cases, debugging, tracing, logging, auditing, monitoring, etc. Notice that the about features incorporated into an application model core will be very helpful in your incrementally development phase, QA and production. Before looking at closely for each component of that mechanism from the application point of view, I’d like to describe how the .Net would interoperate with COM+ Event System.

COM+ Event System.

The COM+ Event System is an unmanaged code – model with COM interfaces packaged into the es.dll component. To enable this code to use in the .Net world we have to generate some metadata for it. The interop layer (RCW) is using this metadata in the run-time to handle an actual COM object (activation, marshaling requirements, etc). MS offers a very useful tool (TlbImp.exe) to generate a metadata from the typelib of unmanaged code, which can be added into the assembly. In my case I choose another way – the reengineering. Based on the EventSys.h file I created abstract definitions of the interfaces needed for their interoperability included their empty classes such as EventSystem and EventSubscription, see the following snippet code:

#region COM+ EventSystem Interfaces 
namespace RKiss.EventSystem
{
  //
  [ComImport, Guid("4E14FBA2-2E22-11D1-9964-00C04FBBB345")]
  public class EventSystem {}
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
      Guid("4E14FB9F-2E22-11D1-9964-00C04FBBB345")]
  public interface IEventSystem
  {
    [PreserveSig]
    object Query([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.BStr)] string queryCriteria, 
      [Out] out Int32 errorIndex);
    [PreserveSig]
    void Store([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.Interface)] object pInterface);
    [PreserveSig]
    void Remove([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [In, MarshalAs(UnmanagedType.BStr)] string queryCriteria, 
      [Out] out Int32 errorIndex);
    [PreserveSig]
    string get_EventObjectChangeEventClassID();  
    [PreserveSig]
    object QueryS([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [Out] out Int32 errorIndex);
    [PreserveSig]
    void RemoveS([In, MarshalAs(UnmanagedType.BStr)] string progId, 
      [Out] out Int32 errorIndex);
  }
  //
  [ComImport, Guid("7542E960-79C7-11D1-88F9-0080C7D771BF")]
  public class EventSubcription {}
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
        Guid("4A6B0E15-2E38-11D1-9965-00C04FBBB345")]
  public interface IEventSubcription
  {
    string SubscriptionID { get; set; }
    string SubscriptionName { get; set; }
    string PublisherID { get; set; }
    string EventClassID { get; set; }
    string MethodName { get; set; }
    string SubscriberCLSID { get; set; }
    object SubscriberInterface { get; set; }
    bool   PerUser { get; set; }
    string OwnerSID { get; set; }
    bool   Enabled { get; set; }
    string Description { get; set; }
    string MachineName { get; set; }
    
    [PreserveSig]
    object GetPublisherProperty([In, MarshalAs(UnmanagedType.BStr)] 
          string PropertyName);
    [PreserveSig]
    void   PutPublisherProperty([In, MarshalAs(UnmanagedType.BStr)] 
          string PropertyName, ref object propertyValue);
    [PreserveSig]
    void   RemovePublisherProperty([In, MarshalAs(UnmanagedType.BStr)] 
         string PropertyName);
    [PreserveSig]
    object GetPublisherPropertyCollection();
    [PreserveSig]
    object GetSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] 
         string PropertyName);
    [PreserveSig]
    void   PutSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] 
          string PropertyName, ref object propertyValue);
    [PreserveSig]
    void   RemoveSubscriberProperty([In, MarshalAs(UnmanagedType.BStr)] 
          string PropertyName);
    [PreserveSig]
    object GetSubscriberPropertyCollection();
    string InterfaceID { get; set; }
  }
}
#endregion

Now, access to the COM+ Event System is transparently and it’s not different from the regular .Net class programming, see the following snippet code:

//Deactivate a subscription in the Event System
public void Deactivate()
  {
    int errorIndex = 0;
    IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
    string strCriteria = "SubscriptionID=" + "{" + guidSub + "}";
    es.Remove("EventSystem.EventSubscription", strCriteria, out errorIndex);
  }

Event Class

This is a design start point and only this assembly will have interoperability with a COM+ services. Here is all magic glue how to use this service in the .Net Framework and it is done by an abstract definition of the Event Interface, Event Class and their attributes.

Interface.

The contract between the publisher and subscriber is represented by IEventWriteLog interface. This abstract has only one method signature with a returned type ‘void’ (nothing to return in the LCE). The interface is public and it will be use in the subscriber design to receive the event call.

Class.

As a next step is a definition of our application Event Class, which the publisher will use to delegate a job to the event system. This class represents a layer between a managed (.Net) world and unmanaged COM+ services. The class has no method implementation and it is derived from the EnterpriseServices.ServicedComponent and IEventWriteLog interface.

Attributes.

There are two kinds of attributes: assembly and class. - The assembly attributes describes an application properties in the COM+ catalog (name, activation, access, etc.). - The class attributes describes a component in the application registered into COM+ catalog (poolable, no transactional, event class). Note that only the EventClassAttribute is a mandatory decoration for the LCE COM+ service, the otherwise the class will not be registered in the Event System Store.

The EventClass implementation is shown in the following snippet code:

using System;
using System.Diagnostics;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
using System.Reflection;
//
using RKiss.EventSystem;

[assembly: AssemblyKeyFile(@"..\..\EventClass.snk")]
[assembly: ApplicationName("EventClassLogger")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(Value = false, 
        Authentication  = AuthenticationOption.None)]

#region COM+ EventSystem Interfaces
// ….
#endregion

namespace RKiss.EventClassLogger
{
  [Guid("050355EC-83C9-4723-9E2D-591AD3CA3B45")]
  public interface IEventWriteLog
  {
    void Write(object e);
  }
  // meta class
  [EventClass(FireInParallel = true)]
  [Guid("F6B2E72A-677F-4356-B33D-C1B5CE1688FA")]
  [Transaction(TransactionOption.Disabled)]
  [ObjectPooling(Enabled=true, MinPoolSize=4, MaxPoolSize=10)]
  [EventTrackingEnabled]
  public class EventClassLog : ServicedComponent, IEventWriteLog
  {
    public void Write(object e)
    {
      Trace.WriteLine(
          "EventClassLog: COM+ LCE system doesn't work properly"); 
    }
  }
}

After compiling this subproject, the EventClass assembly is necessary to install into the Global Assemble Cache (c:\gacutil –i EventClass.dll) and register in the COM+ catalog using the regsvcs.exe utility (c:\regsvcs /tlb EventClass.dll).

Publisher.

In my design the LogMessage Publisher is a light .Net class – gateway to the COM+ LCE notification. It doesn’t require any registration process with the COM+ Event System. As you can see in the following snippet, the application method WriteLog(object msg) is creating an instance of the EventClass and invoking its method Write(msg). Notice that the ‘using’ statement is used to dispose underlying resources in the ServicedComponent, otherwise the COM+ object will be not return back to the pool.

using System;
using System.Diagnostics;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
//
using RKiss.EventClassLogger;

namespace RKiss.EventPublisherLogger
{
  // Publisher
  [Guid("4766CB18-B680-4430-AD9E-56411B73E802")]
  public class PublisherLog 
  {
    // Write Log Messages
    public void WriteLog(object msg)
    {
      using (EventClassLog evt = new EventClassLog()) 
      {
        evt.Write(msg);
      }
    }
  }
}

This is a simple scenario. In the real product, the publisher will have more application methods to hide all wrapping, filtering and formatting arguments into properly message format, for instance in XML. The snippet code might be looked like the following example:

//public void WriteLog(LOG_SEVERITY sev, LOG_CATEGORY cat, 
    string key, string[] msg)
{
    // create a XML Log Message
    long starttime = Environment.TickCount;
    XmlLogMsg  lm = new XmlLogMsg(sev, cat, key, msg);
    StringBuilder xmlStringBuilder = new StringBuilder();
    XmlTextWriter tw = new XmlTextWriter(
         new StringWriter(xmlStringBuilder));
    XmlSerializer serializer = new XmlSerializer(typeof(XmlLogMsg));
    serializer.Serialize(tw, lm);
    string xmlMsg = xmlStringBuilder.ToString();  // export to string
    tw.Close();
    // publish message
    WriteLog(xmlMsg);
}
//
namespace EventXmlLogMessage
{
  public enum LOG_SEVERITY : int { Unknown = 0, Info, Warning, 
              Error, Fatal }
  public enum LOG_CATEGORY : int { Unknown = 0, System, 
              User, Database, Network, 
              Security, Core, Config, Services, Framework }

  // The Log Message object to serialize into XML form 
  [XmlRoot("LogMessage")]
  public class XmlLogMsg 
  {
    [XmlElement(ElementName = "Header")]
    public XmlLogHeader hdr;
    [XmlArray(ElementName = "Body")] 
    [XmlArrayItem(ElementName = "Message")] 
    public string[]  body; 

    public XmlLogMsg() {}
    public XmlLogMsg(LOG_SEVERITY sev, LOG_CATEGORY cat, string key, 
         string[] msg)
    {
      hdr = new XmlLogHeader(sev, cat,key);
      body = msg;
    }
    public class XmlLogHeader
    {
      [XmlAttribute(AttributeName = "Severity")]
      public string  sev;
      [XmlAttribute(AttributeName = "Catalog")]
      public string  cat;
      [XmlAttribute(AttributeName = "Key")]
      public string  key;
      [XmlAttribute(AttributeName = "TickCount")]
      public string  tickcount;
      [XmlAttribute(AttributeName = "TimeStamp")]
      public string  timestamp;

      public XmlLogHeader() {}
      public XmlLogHeader(LOG_SEVERITY sev, LOG_CATEGORY cat, string key)
      {
        this.sev = sev.ToString(); this.cat = cat.ToString(); this.key = key;
        timestamp = DateTime.Now.ToString();
        tickcount = Environment.TickCount.ToString();
      }
    }
  }
}

Now, all tightly design pattern is done. The application can run without any impact in the business process. The publisher will run in the “short circuit” when the Event System will not find a properly target (subscription). So, the next step is to design a component on the consumer side.

Subscriber.

The Subscriber is a managed class with a loosely coupled design pattern based only on the Event interface contract, which is an abstract definition. The subscriber and publisher are “wired” using the Subscription, which is a set of the properties (metadata) to perform the LCE task during the run-time. The Event Subscription is stored in the Event System and based on that, the publisher will know how to invoke the subscriber’s method under the interface contract in the late-binding manner. There are three kinds of form subscriptions: moniker, persistent and transient. In our case, the transient subscription is used, so the consumer (client) has responsibility to register a valid IUnknown pointer, where wants to receive incoming event calls. The other important thing in the subscriber design is how long the incoming event call stayed here (except option FireInParallel). To avoid the thread blocking by some subscribers or slow UI, the event call is queued into the System.Threading.ThreadPool in the ‘fire & forgot’ way. This solution will guarantee a full isolation and minimum overhead between the business process and its post-processing activities. Of course, in some cases, the tightly coupled notification is an advantage and it is requested and then the thread pool is obsolete.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
//
using RKiss.EventSystem;
using RKiss.EventClassLogger;

namespace RKiss.EventSubscriberLogger
{
  //Subscriber
  public delegate void SubscriberLogNotify(object e);
  public class SubscriberLog : IEventWriteLog
  {  
    public event SubscriberLogNotify notify;
    private string guidSub = Guid.NewGuid().ToString();
    public string SubscriptionID { get { return guidSub; }}
    // administration
    public void Activate()
    {
      IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
      IEventSubcription sub = new EventSubcription() as IEventSubcription;
      sub.Description = "Subscription LogMessage";
      sub.SubscriptionName = "SubscriberLog";
      sub.SubscriptionID = "{" + guidSub + "}";
      sub.Enabled = true;
      sub.EventClassID = "{" + Type.GetTypeFromProgID(
         "RKiss.EventClassLogger.EventClassLog").GUID.ToString() + "}";
      sub.MethodName = "Write";
      sub.SubscriberInterface = this;
      es.Store("EventSystem.EventSubscription",  sub);
    }
    public void Deactivate()
    {
      int errorIndex = 0;
      IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
      string strCriteria = "SubscriptionID=" + "{" + guidSub + "}";
      es.Remove("EventSystem.EventSubscription", 
           strCriteria, out errorIndex);
    }
    // IEventWriteLog
    public void Write(object e)
    {
      // fire & forgot
      if(notify != null)
        ThreadPool.QueueUserWorkItem(new WaitCallback(notify), e);
    }
  }
}

The owner of the subscriber is a client, which initiates the subscriber, activating and deactivating its subscription in the Event System and post-processing of the publisher notifications. The subscriber is “wired” with the client using the delegate technique. The publisher notification post-processing in the client represents its business orientation such as monitoring tool, logging, etc.

Tester

The tester is a very simple windows form client to test a COM+ LCE functionality of the about components and showing its usage. There are two independent parts: publisher and subscriber. The client initiates the subscriber and publisher in its ctor:

public Form1()
  {
    InitializeComponent();
    subLog = new SubscriberLog();
    pubLog = new PublisherLog(); 
    eventhandler = new SubscriberLogNotify(OnSubscriberLogNotify);
  }

  private void OnSubscriberLogNotify(object e)
  {
    listBoxSubscriber.Items.Add(e.ToString());
  }

The user interface is very simply solution with two buttons: subscribe/unsubscribe and FireEvent. The first one is used to activate or deactivate the subscription in the Event System and registry delegate in the subscriber event class:

private void buttonSubscriber_Click(object sender, System.EventArgs e)
  {
    try 
    {
      if(buttonSubscriber.Text == "subscribe")
      {  
        subLog.notify += eventhandler;
        subLog.Activate();
        buttonSubscriber.Text = "unsubscribe";
        listBoxSubscriber.Items.Clear();
      }
      else
      {
        subLog.notify -= eventhandler;
        subLog.Deactivate();
        buttonSubscriber.Text = "subscribe";
      }
    }
    catch(Exception ex)
    {
      string strText = ex.GetType() + ":" + ex.Message;
      Trace.Assert(false, strText, 
         "Caught exception at buttonSubscriber_Click");
    }
  }

The other one is to ‘fire event’ – publishing a log message to the Event System.

private void buttonFireEvent_Click(object sender, System.EventArgs e)
  {
    pubLog.WriteLog(textBoxMessage.Text);
  }

Clicking on the ‘FireEvent’ button you will see this message in the active subscriber list box. You can open many Tester applications and play with their subscribers and publishers. Each subscriber has own subscription (random guid), so closing either application or ‘unsubscribe’ will not have any impact to others.

Conclusion.

Using the publisher/subscriber system notification supported by the COM+ Event system service in the .Net Application is very useful for any phase of the product. It will allow to incrementally building a product and its tools in the pyramid fashion. Attributing the Event class, its assembly and using the COM interop layer is very straightforward and making your .Net implementation lightly and understandable.

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

Share

About the Author

Roman Kiss
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
QuestionWindows 7 and .net 4 PinmemberMember 813710511-Sep-12 12:49 
Question"Subscriber" from unmanaged world? Pinmembersensor_in19-Feb-09 1:20 
GeneralProblem on suscriber class Pinmemberjlguerrerosado@gmail.com7-May-08 4:46 
GeneralIn Win2003 Server Pinmemberchomal12-Sep-04 21:30 
Generalgreat article - needs editing Pinmemberlvogel19-Sep-03 11:48 
GeneralPublisherFilter Pinmemberstojko11-Sep-03 11:55 
GeneralRe: PublisherFilter PinmemberSergei_VP4-Feb-04 5:54 
GeneralAn event was unable to invoke any of the subscribers Pinmemberregedit26-Aug-03 3:03 
GeneralSubscriberLog's write method was not invoked. Pinmemberwolga30-Jun-03 6:47 
GeneralRe: SubscriberLog's write method was not invoked. PinmemberRoman Kiss30-Jun-03 17:23 
GeneralRe: SubscriberLog's write method was not invoked. PinsussAnonymous9-May-05 22:39 
GeneralPlease Help PinsussAnonymous9-May-05 22:41 
GeneralRe: SubscriberLog's write method was not invoked. PinmemberMember 813710511-Sep-12 13:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140921.1 | Last Updated 7 Sep 2001
Article Copyright 2001 by Roman Kiss
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid