Contents
The COM+ Event System Notification is based on the loosely coupled Publisher/Subscriber design pattern using the Event
Class metadata (contract) to tight them together during the run time. Using the LCE (loosely coupled events) in the application architecture required to design and implement in prior only the Event
Class and Publisher. The Subscribers are usually part of the incremental development phases. Publishing a business logic and its control workflow allows to enhance an application for features such as post-processing, simulation, debugging, tuning, and monitoring business logic.
Tracing and debugging a COM+ LCE notification (part of the Enterprise services) makes easy using the COM+ Event Subscriber Studio (ESS) Tool described in this article. The ESS Tool is a Virtual Subscriber for any Event Class to examine the fired event calls included their run-time values. This feature is very helpful during the development process and unit testing.
Creating the transient subscription request will dynamically generate a subscriber in memory, link to the user interface and its registration in the COM+ Event System Store. The event calls received by the virtual subscriber are stored into the TreeView layout included their arguments name, type and value such as soap and xml formatted text, dataset, tables and rows. Exploring this TreeView will allow to see a published data by the LCE Publisher source.
In this article I will show you all "magic" of the virtualization using the .Net Framework namespaces such as Reflection and CodeDom. Also you will find a programming technique and usage of the TreeView
, ToolTip
and delegates.
The concept of the Virtual Subscriber is based on generating and compiling its source code on the fly. The source code template of the virtual subscriber is populated in the both sides such as Event System and Consumer in the manner of the delegating the event calls to the destination handler (in our case it's the Windows Form).
The following picture shows a position of the Virtual Subscriber in the COM+ LCE System Notification:
To receive the event call from the Event System, the suitable subscription has to be subscribed to the Event System. The ESS tool is based on the transient subscriptions only. It's fine for our test purposes such as inspecting the event call contents. Creating the subscription is straightforward work if you know all required properties:
Name
- human-readable text describes the name of the subscription (option)
Description
- text describes more details about the subscription (option)
ID
- unique identifier for this subscription (required)
Enabled
- boolean delivery flag to enable/disable this subscription (default is true)
EventClassID
- the Event
Class identifier either the ProgId or clsid (required)
MethodName
- name of the event's method from which subscriber wants to receive incoming event calls (required)
FilterCriteria
- string to filtering events at the Event
Class (option, default value is null
)
SubscriberInterface
- actually reference of the subscriber object, which wants to receive the events, in our case it's the virtual subscriber (required)
PublisherProperty
- the property bag for publisher filter (option, default value is null
)
SubscriberCLSID
- null
for this transient subscription
PublisherID
- null
, we want to receive a specified events from any publisher
InterfaceID
- null
for this transient subscription
SubscriberMoniker
- null
for this transient subscription
The above bolded properties are deceived to create a properly subscription. Based on the user request such as EventClassID
and MethodName
the tool has enough information to create subscription and register it into the Event System Store. The ID is generated by the Guid class, it's easy, but the SubscriberInterface
property required the reference to the existed class - subscriber. The task is simple for the one specific event class and method, which it can be hard coded in the implementation file. In the case of the unknown event class and method, we need to design the virtual subscriber and generate its assembly on the run time.
The following code snippet shows the template of the Virtual Subscriber:
private string strSrcCodeTemplate =
"namespace _NAMESPACE { " +
" using System;" +
" using System.Reflection;" +
" using System.Xml;" +
" using System.Data;" +
" _USING" +
" public class _CLASSNAME { " +
" string m_id=null;" +
" MulticastDelegate m_md=null;" +
" public _CLASSNAME(string id, object handler) {" +
" m_id=id;" +
" m_md=handler as MulticastDelegate;" +
" }" +
" public void _METHODSIGNATURE {" +
" object[] args=new object[]{_ARGUMETS};" +
" if(m_md!=null)" +
" m_md.Method.Invoke(m_md.Target,
new object[]{m_id, args});" +
" }" +
" }" +
"}";
As you can see, it's a very lightweight class. The bolded text represents its virtualization. These places are replaced by the actually values on the fly. The incoming event calls will perform the following:
- serializing name and value of the method's arguments to the object array
- invoking the delegate method
These couple lines in the event method makes powerful mapping of the unknown event method to the generic known target (consumer) handler. I used a "magic" delegate/event design pattern to forward the event calls to the consumer side, however I don't using the event class to store a delegate object. I just casting a delegate object to the MulticastDelegate type and simple invoking a method on the target object. This trick makes a loosely coupled design for the consumer side usage.
After compiling the populated subscriber template source code and creating its instance using the Activator
class, we obtained the reference to the subscriber object requested by the transient subscription (SubscriberInterface
).
Registering the subscription in the Event
System Store we are the part of the Event System Model and ready to receive particularly event calls. This is a first part of the COM+ Event Subscriber Studio - Subscription Administrator.
The second part of the ESS Tool is dedicated to incoming event calls. Its design is straightforward and user interface oriented, based on the TreeView
controller. The event calls are received by the handler delegated to the virtual subscriber using the following delegator signature:
public delegate void NotifyHandler(string id, object[] p);
where:
id
specifies the subscription identifier generated by the ESS Subscription Administrator
p
specifies array of the event call arguments - name and value.
Based on the above parameters, the TreeView
controller is populated using the icon and label properties. Note that the virtual subscriber runs on the different thread than the TreeView
controller, that's way the marshalling is necessary to perform it using the Invoke
design pattern.
The ESS Tool has been implemented by the following two assemblies:
- VirtualSubscriber.dll - This is a reusable class to generate a subscriber during the run-time. It can be used also to create a specific subscriber in .Net Application.
- SubscriptionStudio.exe - This is a Windows Form program to handle administration of the subscription and displaying the fired event calls.
Implementation of the Virtual Subscriber is based on the single class - SubscriberFactory
, which has only one public method to create an instance of the subscriber with specified arguments. The object reference of the subscriber is returned back to the caller.
The following code snippet show that:
public object CreateInstance(string id, MethodInfo miEvent, object handler,
string[] refUsing, string[] refAsm)
{
string thisNamespace = this.GetType().Namespace;
string className = "Subscriber" + "_" + this.GetHashCode().ToString();
string source = CreateSourceCode(thisNamespace, refUsing,
className, miEvent);
Assembly asm = CompileFromSource(source, refAsm);
Type tVS = asm.GetType(thisNamespace + "." + className);
return Activator.CreateInstance(tVS, new object[]{id, handler});
}
public object CreateInstance(string id, MethodInfo miEvent, object handler)
{
return CreateInstance(id, miEvent, handler, null, null);
}
Its logic is very simple using the helper methods such as CreateSouceCode
and CompileFromSource
.
The first one will populate a hard-coded subscriber template by replacing its special words (_NAMESPACE, _USING, _CLASSNAME, _METHODSIGNATURE, _ARGUMETS
) with actually strings. The Reflection
namespace classes are using to obtain a type and name of the event method's arguments necessary to accomplish this task .
private string CreateSourceCode(string strNs, string[] references,
string strClassName, MethodInfo mi)
{
StringBuilder sbSrc = new StringBuilder(strSrcCodeTemplate);
sbSrc.Replace("_NAMESPACE", strNs);
if(references != null)
{
StringBuilder sbUsing = new StringBuilder();
foreach(string s in references)
{
if(s != null && s.Length > 0)
sbUsing.AppendFormat("using {0};", s);
}
sbSrc.Replace("_USING", sbUsing.ToString());
}
else
{
sbSrc.Replace("_USING", "");
}
sbSrc.Replace("_CLASSNAME", strClassName);
StringBuilder sbMS = new StringBuilder();
StringBuilder sbArgs = new StringBuilder();
sbMS.AppendFormat("{0}(", mi.Name);
foreach(ParameterInfo pi in mi.GetParameters())
{
sbMS.AppendFormat("{0} {1} ,", pi.ParameterType.Name, pi.Name);
sbArgs.AppendFormat("\"{0}\", {1},", pi.Name, pi.Name);
}
sbMS.Remove(sbMS.Length - 1, 1);
sbMS.Append(")");
sbArgs.Remove(sbArgs.Length - 1, 1);
sbSrc.Replace("_METHODSIGNATURE", sbMS.ToString());
sbSrc.Replace("_ARGUMETS", sbArgs.ToString());
return sbSrc.ToString();
}
When we have an actually source code, we can call the following helper function to obtain its assembly:
private Assembly CompileFromSource(string source, string[] refasm)
{
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
if(refasm != null)
cp.ReferencedAssemblies.AddRange(refasm);
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.IncludeDebugInformation = false;
ICodeCompiler icc = new CSharpCodeProvider().CreateCompiler();
CompilerResults cr = icc.CompileAssemblyFromSource(cp, source);
if(cr.Errors.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach(CompilerError ce in cr.Errors)
{
sb.Append(ce.ErrorText);
}
throw new Exception(string.Format("Build failed: {0}",
sb.ToString()));
}
return cr.CompiledAssembly;
}
As you can see, compiling the source code on the fly is straightforward using the .Net Framework namespaces such as CodeDom
, CodeDom.Compiler
and Microsoft.CSharp
. You can also add additional assemblies via the argument refasm. In the case of compiling error(s), the exception is thrown with passing more error details to its caller.
This GUI of the ESS Tool is divided into two panels and both of them are driven by TreeView Controllers. The left panel is dedicated to administrate subscriptions and the right one is for displaying received event calls. I am going to focus on the part related to the Virtual Subscription, only. Clicking on the node which allows to register subscription, the following handler function is performed:
private void Click_Register(object sender, System.EventArgs e)
{
try
{
string id = subscriptionName + "[" + subscriptionId + "]."
+ methodName;
MethodInfo mi = typeofEventClass.GetMethod(methodName);
SubscriberFactory sf = new SubscriberFactory();
subscriber = sf.CreateInstance(id, mi, this.m_EventHandler,
references, dlls);
IEventSystem es = new EventSystem.EventSystem() as IEventSystem;
IEventSubcription2 sub = new EventSubcription() as IEventSubcription2;
sub.SubscriptionName = subscriptionName;
sub.Description = subscriptionDesc;
sub.SubscriptionID = "{" + subscriptionId + "}";
sub.Enabled = true;
sub.EventClassID = "{" + typeofEventClass.GUID.ToString() + "}";
sub.MethodName = methodName;
sub.FilterCriteria = filterCriteria;
sub.SubscriberInterface = subscriber;
es.Store("EventSystem.EventSubscription", sub);
tn.ImageIndex = 1;
tn.SelectedImageIndex = tn.ImageIndex;
UpdateSubscriptionMenu(tn);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, m_thisProcess.ProcessName,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
The above code snippet shows an interaction with the virtual subscriber to obtain its object reference and creating the subscription object and its registration in the COM+ event System.
The right panel GUI is a background driven task. Each time the event call is received, its handler adding a new node to its TreeView root. This child node has own node layout depends from the type and numbers of the event's arguments.
The following code snippets show creating the handler delegator and its callback method - OnNotifyHandler
:
Declaration
private delegate int AddToTreeView(TreeNode node);
private delegate void NotifyHandler(string id, object[] p);
private object m_EventHandler = null;
Initializing
public Form1()
{
m_EventHandler = new NotifyHandler(OnNotifyHandler);
}
Handling
private void OnNotifyHandler(string id, object[] p)
{
TreeNode tnRoot = treeViewEvent.Nodes[0];
try
{
TreeNode tnParent = new TreeNode(id, 0, 1);
for(int ii = 0; ii < p.Length; ii++)
{
string name = p[ii++].ToString();
object obj = p[ii];
TreeNode tnArg = new TreeNode(name, 2, 2);
if(obj is DataSet)
{
AddDataset(tnArg, obj);
}
else
if(obj is string)
{
AddText(tnArg, obj);
}
else
{
AddObject(tnArg, obj);
}
tnParent.Nodes.Add(tnArg);
}
if(treeViewEvent.InvokeRequired == true)
treeViewEvent.Invoke(new AddToTreeView(tnRoot.Nodes.Add),
new object[] {tnParent});
else
tnRoot.Nodes.Add(tnParent);
}
catch(Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
Using the COM+ Event Subscription Studio is not complicated task. There is one root - Transient Subscriptions to hold a collection of the subscription nodes. Each node included the root one has own state and pop menu to select a particular action. For instance, the root node offering to add a new subscription or delete all of them. The icon of the subscription node indicated its state. The following picture shows these states:
When subscription is unregistered or new, its properties can be modified using the formula entry name = value [, ]
where comma is delimiter between values (needed for Ref and Dll only).
After registering subscription, the ESS Tool is ready to receive and display incoming event calls on the right panel under the Fired Events root. Expanding the nodes, we can inspected the particular event call arguments for their types and values.
The following picture shows some types and values of the event call:
Exiting the ESS Tool the following process is performed:
- unregistering all registered subscriptions from the COM+ Event System.
- storing the collection of the subscription nodes in the SubscriptionStudio.xml file, which can be loaded back during the initializing process.
I created a solution to test the ESS Tool. There is a set of the following projects:
SubscriptionStudio
- Windows Form, program
VirtualSubscriber
- subscriber on the fly, library
EventClassSample
- event class, library
PublisherSample
- publisher to fire an event, program
Note that the last 2 projects have been designed and implemented only for test purposes.
Follow these steps to make your first test:
- Step 1. Launch the PublisherSample program. If this is a first time, then click the button FireEvent to perform a lazy registration of the Event Class in the COM+ Catalogue
- Step 2. Launch the SubscriptionStudio program.
- Step 3. Expand the main root.
- Step 4. Right click on the Subscription node and select Register. You will see the node icon has been changed.
- Step 5. Fire Event from the Publisher
- Step 5. Exanimate the received event call in the right panel of the ESS Tool.
The COM+ Event Subscription Studio is a useful tool for developers, whose are building the COM+ Event Publisher/Subscriber system notifications. This article described its "heart" - Virtual Subscriber and programming technique of the loosely coupled design pattern. The GUI part is a lite version. The best solution is incorporated windows controllers into the MMC environment and extended for virtualization also the Event Classes and Publishers included their test of the firing events.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.