Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / ATL
Article

COM+ Subscription Viewer

Rate me:
Please Sign up or sign in to vote.
4.40/5 (11 votes)
19 Nov 20015 min read 101.3K   1.2K   36   6
This sample shows how to retrieve a subscriptions registered in the Event System store using C#.

Introduction

The Publisher/Subscriber loosely coupled event (LCE) system notification is a part of the COM+ services. This service is based on the meta data of the Event Classes and Subscriptions located in the Event System. During the run-time, the meta data are used by the Publisher and Event System to invoke the Event method on the Subscriber. The Subscription (logical connection to the Event Class) can be created programmatically or administratively using the Management Console (MMC) for Component Services. Note that the Subscriptions, which have been created programmatically, are invisible in the MMC. There is no feature to view them in the MMC. This article describes in the simple sample how to retrieve the Subscriptions from the Event System using the C# language.

Design

The Subscriptions can be retrieved from the Event System storage using the following design techniques:

  • Pooling mechanism based on the user request
  • Event driven mechanism based on their changes

The following picture shows both design patterns:

Image 1

The Event System has built-in a publisher to notify subscribers about the changes such as ChangedSubscription, ChangedEventClass and ChangedPublisher. To receive this notification, the Subscriber needs to create a properly subscription and register it into the Event System. Access to the Event System (properties, collection of objects, etc.) is available via its interface IEventSystem contract.

The Subscriber in the about design has the following tasks:

  • Creating and registering/unregistering the Subscription to receive a notification of the changes in the Event System
  • Retrieving the properties of all subscriptions from the EventObjectCollection and formatting them into the XML string
  • Removing a specified subscription from the EventObjectCollection object.
  • Passing the received event calls from the Event System Publisher to the user side using the event/delegate design pattern

The user side is a simple Windows Form with a treeview control to display a properties of the subscriptions in the asynchronously manner. There are to buttons; first one is to refresh the treeview nodes and the other one is for removing a selected subscription from the Event System.

There are two scenarios in the design. First one will retrieve the Subscriptions by request - pressing the button Refresh (this is a pooling feature). The other one is based on triggering the Refresh by the Event System notification. This notification will refresh the treeview nodes the same way as first scenario. Note that the active Publisher and Subscriber are tightly coupled and the notification process is serialized thru all subscribers, that's why the user form handling the notification in the asynch and isolated fashion.

Implementation

The implantation of the about design is divided into two parts; the Subscriber and User Interface. Both components are located in the managed code. In the following snippet codes I will describe some important parts of their implementation.

Event System

Access to the Event System (unmanaged code packed into the component es.dll) is based on the COM Interop features, which is an integral part of the .Net Framework. The Subscriber needs to import the typelib EventSystemLib of the Event System into its assembly. This meta data allows accessing to the unmanaged code full transparently using an early binding Reflection pattern design. The following snippet code shown the contract with the Event System:

C#
using EventSystemLib;    // created by tlbimp es.dll

[ComImport, Guid("4E14FBA2-2E22-11D1-9964-00C04FBBB345")]
class EventSystem {}

[ComImport, Guid("7542E960-79C7-11D1-88F9-0080C7D771BF")]
class EventSubcription {}

[ComImport, Guid("AB944620-79C6-11d1-88F9-0080C7D771BF")]
class EventPublisher {}

[ComImport, Guid("cdbec9c0-7a68-11d1-88f9-0080c7d771bf")]
class EventClass {}

Note that the meta data of the classes have to be defined programmatically using the re-engineering methodology.

Subscription

The Subscriber Activation needs to perform a bridge between the Event System and User Interface (Form). This action is done registering a delegate handler to the event object (wiring process) and from the other side, creating and registering the Subscription into the Event System. Note that the Event Class for this Subscription has to be obtaining from the Event System, also in this design the Subscription is a transient type, that's why the sub.SubscriberInterface = this;

C#
public void Activate(Object wire)
{
  // activate subscriber
  try
  { // register delegate into the event object
    notify += wire as EventSystemNotificationHandler;
    //
    //access to Even System (LCE)
    IEventSystem es = new EventSystem() as IEventSystem;
    //create and populate a subscription object
    IEventSubscription sub = new EventSubcription() as IEventSubscription;
    sub.Description = SubscriptionViewerDesc;
    sub.SubscriptionName = SubscriptionViewerName;
    sub.SubscriptionID = SubscriptionViewerID;
    sub.methodName = "ChangedSubscription";
    sub.SubscriberInterface = this;
    sub.EventClassID = es.EventObjectChangeEventClassID;
    es.Store("EventSystem.EventSubscription", sub);
  }
  catch(Exception ex)
  {
    string strText = ex.GetType() + ":" + ex.Message;
    Trace.Assert(false, strText,
     "Caught exception at SubscriberViewer.Subscriber.Activite");
  }
}

 

Subscriber Refresh

The Refresh method is a key function of the Subscriber. It provides an actually business logic, which is retrieving the properties of the Subscriptions in the Event System collection object and formatting them into the XML string as the returned value. Its implementation is using the XMLTextWriter class to populate the XML nodes. Note that the COM objects have to be released in the finally place using the Marshal.ReleaseComObject function.

C#
  public string Refresh()
  { 
    // access to ES subscriptions
    IEventObjectCollection evntObjColl = null;
    IEnumEventObject evnObj = null;
    IEventSubscription2  subscr = null;
    // build a string writer for XmlTextWriter class
    string xmlRetVal = "";
    StringBuilder xmlStringBuilder = new StringBuilder();
    StringWriter  xmlStringWriter = new StringWriter(xmlStringBuilder);
    XmlTextWriter tw = new XmlTextWriter(xmlStringWriter);

    try 
    {    
      int errorIndex = 0;
      // interface to the Event System (LCE)
      IEventSystem es = new EventSystem() as IEventSystem;
      // get the collection of the subscription
      evntObjColl = es.Query("EventSystem.EventSubscriptionCollection",
                             "ALL", out errorIndex ) 
            as IEventObjectCollection;
      evnObj = evntObjColl.NewEnum as IEnumEventObject;
      //
      //
      int numberOfObj = evntObjColl.Count;        
      //create XML string
      tw.WriteStartDocument();                // Start Document
      tw.WriteComment(
    "Catalog of all Subscription registered in the Event System Store");
      tw.WriteStartElement("Subscriptions");  // Start Root
      //
      while(true)
      {    
        object objSubscr = new object();
        uint retCount = 0;
        evnObj.Next(1, out objSubscr, out retCount);
        if(retCount == 0) break;
        subscr = objSubscr as IEventSubscription2;
        tw.WriteStartElement("Subscription"); // start Node
        tw.WriteAttributeString("SubscriptionName", subscr.SubscriptionName);
        tw.WriteAttributeString("SubscriptionID", subscr.SubscriptionID);
        // Elements
        tw.WriteElementString("Description", "", subscr.Description);
        tw.WriteElementString("Enabled", "", subscr.Enabled.ToString()); 
        tw.WriteElementString("EventClassID", "", 
           Type.GetTypeFromCLSID(new Guid(subscr.EventClassID)).ToString());
        tw.WriteElementString("InterfaceID", "", subscr.InterfaceID);
        tw.WriteElementString("methodName", "", subscr.methodName);
        tw.WriteElementString("MachineName", "", subscr.MachineName);
        tw.WriteElementString("PerUser", "", subscr.PerUser.ToString());
        tw.WriteElementString("OwnerSID", "", subscr.OwnerSID);
        tw.WriteElementString("PublisherID", "", subscr.PublisherID);
        tw.WriteElementString("SubscriberMoniker", "", 
                 subscr.SubscriberMoniker);
        tw.WriteElementString("FilterCriteria", "", subscr.FilterCriteria);
        if(subscr.SubscriberCLSID != null)
          tw.WriteElementString("SubscriberCLSID", "", 
Type.GetTypeFromCLSID(new Guid(subscr.SubscriberCLSID)).ToString());
        else
          tw.WriteElementString("SubscriberCLSID", "", "null");
        if(subscr.SubscriberInterface != null)
          tw.WriteElementString("SubscriberInterface", "", 
                subscr.SubscriberInterface.ToString());
        else
          tw.WriteElementString("SubscriberInterface", "", "null");
        tw.WriteEndElement();                     // end Node
        // cleam-up
        Marshal.ReleaseComObject(subscr);  // release IEventSubscription;
        subscr = null;
      }    
      tw.WriteEndElement();                       // End Root        
      tw.WriteEndDocument();                      // End Document
      //
      xmlRetVal = xmlStringBuilder.ToString();    // export to string
    }
    catch(Exception ex)
    {
      string strText = ex.GetType() + ":" + ex.Message;
      Trace.Assert(false, strText, 
          "Caught exception at SubscriberViewer.Subscriber.Refresh");
    }
    finally
    {
      // dereference COM interfaces
      if(evntObjColl != null)
        Marshal.ReleaseComObject(evntObjColl);    
      if(evnObj != null)
        Marshal.ReleaseComObject(evnObj);
      if(subscr != null)
        Marshal.ReleaseComObject(subscr);
    }
                
    return xmlRetVal;
  }

 

IEventObjectChange

This is a callback interface contract to notify the Subscriber Sink about the changes in the Event System. In this sample I used only one notification - ChangedSubscription. This callback method delegates the notification to the user side, see the following snippet code:

C#
/// <summary>
/// IEventObjectChange
/// </summary>
public void ChangedSubscription(EOC_ChangeType dwChangeType, string str)
{
  string strText = dwChangeType.GetType()+ "=" + dwChangeType.ToString()
                                         + "\n" + "CLSID=" + str;
  notify(this, strText);
}
public void ChangedEventClass(EOC_ChangeType dwChangeType, string str)
{
  // not implemented
}
public void ChangedPublisher(EOC_ChangeType dwChangeType, string str)
{
  // not implemented
}

Windows Form

This is a simple User Interface to the Subscriber. There is one key method, which invokes the Subscriber Refresh method to obtain the XML string - strRetVal and mapping to the treeview nodes. Using the XML classes and treeview control the implementation is straightforward and readable.

C#
protected void OnRefresh (object sender, System.EventArgs e)
{
  try
  {
    treeView.Nodes.Clear();
    // ask agent for all subscriptions stored in the ES
    string strRetVal = agent.Refresh();
    // xml message -> treeview
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(strRetVal);
    XmlNodeList NodeList = doc.GetElementsByTagName("Subscription");

    for(int ii = 0; ii < NodeList.Count; ii++)
    {
      XmlNode subscription = NodeList.Item(ii);
      string strNodeName = subscription.Attributes.Item(1).Value + ", ";
      strNodeName += subscription.Attributes.Item(0).Value;
      TreeNode arrChild = new TreeNode(strNodeName);
      foreach(XmlNode iNode in subscription)
      {
        if(iNode.NodeType == XmlNodeType.Element)
        {
          string iNameValue = iNode.Name + "=";
          iNameValue += iNode.InnerText;
          arrChild.Nodes.Add(iNameValue);
        }
      }
      if(treeView.InvokeRequired == true)
            // check if we running within the same thread
        treeView.Invoke(new AddToTreeView(treeView.Nodes.Add),
                 new object[] {arrChild});
      else
        treeView.Nodes.Add(arrChild);
    }
  }
  catch(Exception ex)
  {
    String str = ex.GetType() + " : " + ex.Message;
    Trace.Assert(false, str, "Caught exception at OnRefresh");
  }

  buttonRemove.Hide();
}

 

The Windows Form callback function

This is an end point of the Publisher's notification call. As I mentioned early, the Publisher needs to perform this call fast with minimum blocking, that's why the thread has been swap into the thread pool using the BeginInvoke/EndInvoke design pattern called as "fire & forgot".

C#
/// <summary>
/// Callback function hooked to the subscriber agent
/// </summary>
protected void EventSystemNotification(object sender, Object e)
{
  // spawn thread
  RefreshProc proc = new RefreshProc(OnRefresh);
  IAsyncResult result = proc.BeginInvoke(sender, null,
                  new AsyncCallback(OnRefreshDone), null);
}
protected void OnRefreshDone(IAsyncResult ar)
{
  // cleanup
  RefreshProc proc = ((AsyncResult)ar).AsyncDelegate as RefreshProc;
  proc.EndInvoke(ar);
}

User Interface

The following picture shows the Subscription properties in the treeview control. Clicking on the button Remove, this Subscription can be removed from the Event System. Any real-time changes in the Subscription's collection object in the Event System will cause its refreshing. Note that each Subscriber has own unique Id (generated random Guid), which it allows to have more Viewer independent instances running the same time.

Image 2

Conclusion

Using the LCE COM+ service in the .Net Application is full transparently. In this simple sample it has been shown how to "talk" with the Event System unmanaged code based on their interface contract. The Subscription Viewer (this lite version) is a valuable utility for monitoring the Event System store during the developing or production phases.

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

 
QuestionBegin Reading session !!! Pin
Mahfoud BOUKERT3-Aug-16 4:31
Mahfoud BOUKERT3-Aug-16 4:31 
GeneralThe quality of code Pin
Mimino4-Sep-05 2:17
Mimino4-Sep-05 2:17 
GeneralInvoke Thread Pin
Vahe Karamian1-Mar-05 14:22
Vahe Karamian1-Mar-05 14:22 
GeneralMarshal.ReleaseComObject Pin
ChrisHubbard18-Nov-04 4:55
ChrisHubbard18-Nov-04 4:55 
GeneralD:\complusEnotification\Viewer\ViewerForm.cs(63): 'Viewer.ViewerForm.Dispose()' : cannot override inherited member 'System.ComponentModel.Component.Dispose()' because it is not marked virtual, abstract, or override Pin
J RANGARAJAN5-Jul-04 20:51
J RANGARAJAN5-Jul-04 20:51 
GeneralRe: D:\complusEnotification\Viewer\ViewerForm.cs(63): 'Viewer.ViewerForm.Dispose()' : cannot override inherited member 'System.ComponentModel.Component.Dispose()' because it is not marked virtual, abstract, or override Pin
Roman Kiss12-Jul-04 7:03
Roman Kiss12-Jul-04 7:03 

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.