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:
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:
using EventSystemLib;
[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;
public void Activate(Object wire)
{
try
{
notify += wire as EventSystemNotificationHandler;
IEventSystem es = new EventSystem() as IEventSystem;
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.
public string Refresh()
{
IEventObjectCollection evntObjColl = null;
IEnumEventObject evnObj = null;
IEventSubscription2 subscr = null;
string xmlRetVal = "";
StringBuilder xmlStringBuilder = new StringBuilder();
StringWriter xmlStringWriter = new StringWriter(xmlStringBuilder);
XmlTextWriter tw = new XmlTextWriter(xmlStringWriter);
try
{
int errorIndex = 0;
IEventSystem es = new EventSystem() as IEventSystem;
evntObjColl = es.Query("EventSystem.EventSubscriptionCollection",
"ALL", out errorIndex )
as IEventObjectCollection;
evnObj = evntObjColl.NewEnum as IEnumEventObject;
int numberOfObj = evntObjColl.Count;
tw.WriteStartDocument();
tw.WriteComment(
"Catalog of all Subscription registered in the Event System Store");
tw.WriteStartElement("Subscriptions");
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");
tw.WriteAttributeString("SubscriptionName", subscr.SubscriptionName);
tw.WriteAttributeString("SubscriptionID", subscr.SubscriptionID);
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();
Marshal.ReleaseComObject(subscr);
subscr = null;
}
tw.WriteEndElement();
tw.WriteEndDocument();
xmlRetVal = xmlStringBuilder.ToString();
}
catch(Exception ex)
{
string strText = ex.GetType() + ":" + ex.Message;
Trace.Assert(false, strText,
"Caught exception at SubscriberViewer.Subscriber.Refresh");
}
finally
{
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:
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)
{
}
public void ChangedPublisher(EOC_ChangeType dwChangeType, string str)
{
}
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.
protected void OnRefresh (object sender, System.EventArgs e)
{
try
{
treeView.Nodes.Clear();
string strRetVal = agent.Refresh();
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)
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".
protected void EventSystemNotification(object sender, Object e)
{
RefreshProc proc = new RefreshProc(OnRefresh);
IAsyncResult result = proc.BeginInvoke(sender, null,
new AsyncCallback(OnRefreshDone), null);
}
protected void OnRefreshDone(IAsyncResult ar)
{
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.
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.