(f) Notify others when your application change state
Loosely Coupled Events (LCE) is one of the COM+ features that can be very
useful. LCE enable COM+ component to initiate event that eventually will be
raise on unknown clients. Clients should be register as COM+ event component
subscriber, by using COM+ MMC or dynamically. Publisher calls one of the COM+
event components methods that cause event to be send and rose in all of the
subscribers. The contract between event component subscribers and publisher is
single interface that should be implements by all of the contract parties.
LCE can be used to create notification to components or application without
knowing in advance who will be those applications or components. In order to
enable those applications or components to get notifications they need to
implement that event interface and subscribe to the COM+ event component. Every
other task�s that should be dome are being taken care of by COM+.
To simulate the power of LCE we will create mechanism that enable desire web
application to get notification when certain Database table changed by one of
web applications or any other application. To enable that mechanism we need to
control all of the applications access to database. This task can be achieved by
creating new COM+ server application that implement Select, Insert, Update and
Delete functions. Every data layer class in every application should use this
class when sending SQL statements to database. That database pipe class is
actually the publisher. Every database command will end up with call to class
register as COM+ library and serve as the event class. Every web application
that wants to be notifies needs to implement subscriber class and register it
(subscriber class) as the event subscriber.
Let's start with the event component � LCEventLibrary.Dll. It's a simple
assembly containing the interface and a class that register as COM+ library
serving as event class using EventClass attribute. Note that this class
shouldn�t implement anything and shouldn�t be call directly. This class is used
by COM+ to call subscribers.
Figure 5.0
public interface IMySink
{
void OnDBChange(string Action, string DBEntity);
}
[EventClass(FireInParallel = true)]
public class MyEventClass : ServicedComponent,IMySink
{
public void OnDBChange(string Action, string DBEntity)
{
throw(new
NotImplementedException("You should not call an event class directly! "));
}
}
To enable Web application subscription to MyEventClass we will create service
class (TransientSubscription) that encapsulates all the lines that should be
write to add subscriber dynamically. Dynamic subscribing can be done by
manipulating COM+ metadata. To manipulate COM+ metadata we need to use COM+ COM
+ 1.0 Admin Type Library so I use tlbimp.exe to create managed wrapper for that
COM library. TransientSubscription expose static function to add and remove
subscribers. To add subscriber one should send unique name, the event class type
and subscriber class. Sink interface can be found by looping through subscriber
class interfaces.
static public void Add(string subName,Type eventClass,Type sinkInterface,
string method,object subscriber)
{
Type sinkType = subscriber.GetType();
if(method != "")
{
MethodInfo info = sinkInterface.GetMethod(method);
}
ICOMAdminCatalog catalog;
ICatalogCollection transientCollection;
ICatalogObject subscription = null;
catalog = (ICOMAdminCatalog)new COMAdminCatalog();
transientCollection =
(ICatalogCollection)catalog.GetCollection("TransientSubscriptions");
subscription = (ICatalogObject)transientCollection.Add();
subscription.set_Value("Name",subName);
subscription.set_Value("SubscriberInterface",subscriber);
string eventClassString = "{"+eventClass.GUID.ToString()+"}";
subscription.set_Value("EventCLSID",eventClassString);
string sinkString = "{" +sinkInterface.GUID.ToString()+ "}";
subscription.set_Value("InterfaceID",sinkString);
subscription.set_Value("MethodName",method);
subscription.set_Value("FilterCriteria","");
subscription.set_Value("PublisherID","");
transientCollection.SaveChanges();
}
The subscriber class (MySubscriber
) is implemented as part of the web
application. MySubscriber
implements IMySink
interface and write the code that
should be called when the event class calls its subscribers. In this demo I
simply update application variable that something changed. One problem that
needs workaround is updating Application object. I can�t use HTTPContext.Current
since subscriber call isn�t connect to any request. To workaround that problem I
add constructor that get HttpApplicationState
as parameter and set internal
member to it.
public class MySubscriber : LCEventLibrary.IMySink
{
private System.Web.HttpApplicationState App;
public MySubscriber()
{
}
public MySubscriber(System.Web.HttpApplicationState inApp)
{
App = inApp;
}
public void OnDBChange(string Action, string DBEntity)
{
App["DbChange"] = true;
}
}
The actual subscribing happened in Application_start
event.
m_Subscriber = new MySubscriber(System.Web.HttpContext.Current.Application);
TransientSubMgr.TransientSubscription.Add
("MySub",typeof(LCEventLibrary.MyEventClass),m_Subscriber);
Application["DbChange"] = false;
PublisherClass
assembly contains PubClass
class that act as the publisher.
PubClass
serve as single connection point to database thus register as COM+
application with methods for all database actions. Every method create instance
of MyEventClass
based on IMySink
Interface and call
OnDBChange
with right
parameters. UpdateData
extract table name from SQL statement and send it as
parameter.
public class PubClass : System.EnterpriseServices.ServicedComponent
{
public PubClass()
{
}
public void UpdateData(string SQL)
{
LCEventLibrary.IMySink sink;
sink = new LCEventLibrary.MyEventClass();
string[] arr = SQL.Split (' ');
sink.OnDBChange("Update",arr[1]);
}
To activate the publisher I add new page (CallPublisher.aspx) to the web
application. That page contain two buttons one for calling PubClass UpdateData
Method and one to simply refresh the page. On page_load
I check for application
DbChange variable. If it set to true one of the database actions execute and
Notification print into the render HTML.
private void Page_Load(object sender, System.EventArgs e)
{
if ((bool)Application["DbChange"] == true)
{
Response.Write ("DB data changed!");
}
}
private void Button1_Click(object sender, System.EventArgs e)
{
PublisherClass.PubClass oPC = new PublisherClass.PubClass();
oPC.UpdateData("update MyTable set MyField=value");
}
private void Button2_Click(object sender, System.EventArgs e)
{
}
Running the page for the first time will show the form with just the two
buttons. Clicking on Update button will cause publisher to call event class
method followed by calling to all subscribers. The subscriber class in that web
application will update given application variable. Pushing the refresh button
now will end up with page containing text notification about the change.
(g) Picking COM+ fruit without need to nourish COM+ tree.(COM+ 1.5 / CLR
1.1)
COM+ 1.5 introduce new working paradigm that enables classes builders to use
part of COM+ services without registering classes as COM+ application (Services
Without Components - SWC). That ability introduced to C++ developers that
performance is their main concern. C++ developers can use CServiceConfig
class
to query interface of one of the services and then use APIs to use the chosen
COM+ services as inline block or in batch mode.
Although SWC can be used by .NET application using interop CLR 1.1 introduce
new class (ServiceConfig) in EnterpriceServices namespace that enable using COM+
SWC using .NET objects. ServiceConfig simply encapsulate the needed API
functions to enable SWC. Using of this class if straight forward. You need to
create an instance, set the features that you want to use by setting attributes,
use Enter and Leave to mark the block that will use those COM+ features and
write code inside that block.
public void HandleUser(string UID)
{
DataLayerClass oDLC = new DataLayerClass();
try
{
ServiceConfig sc = new ServiceConfig();
sc.Transaction = TransactionOption.RequiresNew;
sc.TransactionTimeout = 30;
sc.TrackingAppName = "SWC Test";
sc.TransactionDescription = "Test";
sc.TrackingEnabled = true;
ServiceDomain.Enter(sc);
oDLC.DeleteUser(UID);
ContextUtil.SetComplete();
}
catch(Exception ex)
{
ContextUtil.SetAbort();
}
finally
{
ServiceDomain.Leave();
}
}
As I mention SWC enable just part of COM+ services. To be more accurate those
are COM+ services that might be use by SWC:
- COMTIIntrinsic.
- IISIntrinsic.
- Context switching.
- Isolation level.
- Partitions
- Side by side assemblies
- Synchronization
- Thread pool
- Transactions.
Although its long list of attributes the most useful services such as Object
pooling, JITA, Queued Components and Loosely coupled events aren�t include. As
you saw throughout this article those COM+ features used to solve daily problems
and to turn your application to be more robust and available.
(h) Notify ASP.NET from COM+ server application.
If you convinced that registration Class as COM+ server application can turn
your application to become more stable and available you need also to remember
the limitations that I already mention. One cause for limitation is the need to
use Remoting between the COM+ component and the caller application. If you call
COM+ component Remoting is done by CLR for you but if you need to call
Application from COM+ application some work should be done in order to enable
it.
One of the common scenarios is call back events from COM+ application to the
calling application. When COM+ application needs to call your application its
now your application turns to serve as Remoting server. To enable your
application to behave as Remoting server you need to register to TCP port.
Let�s take a real life situation and see how we can do it with COM+ server
applications and callback events. Suppose you need to read from database very
large amount of data, manipulate that data and display all the data to the user.
If we will follow the default page process paradigm we will get all the data
format it and send it to the client in single buffer. We will supply very slow
application to the end user. When we need to deal with large amount of data its
better to read chunk of total data, format it and send it directly to the user.
Using this paradigm will give the use sense of well operated system. The user
quickly can see some data and as the time grows he can see more and more data
until all the data send.
To enable that page process sequence we need to A- set buffering to false, so
we can send chunks of data. B- Read data from Database using DataReader, collect
chunk of records, format them and send them to the client. To make our
application more stable its better to put data reading in class that will be
register as COM+ application. To enable COM+ components to send chunks of data
to the application we will implement call back events that will be notify the
caller when chunk of data ready to be format and send.
The sample project is made from new web application
(ASPNET_COMPLUSE_CALLBACK), regular assembly (ComPlusFacade) and COM+ server
application class (ComPlusLib). ComPlusFacade task is to get the COM+ component
events and write the received data to output buffer. ComPlusFacade is separate
assembly because both the caller application and called COM+ component needs to
be in GAC to enable each and every one of them to use other. Before running this
sample, don�t forget to register those assemblies in GAC.
Figure 6.0
ComPlusLib contain two classes: XMLArg that decorate with Serializable
attribute to enable transfer of event argument over Remoting channel.
[Serializable()]
public class XMLArg : EventArgs
{
string XMLBuffer;
public XMLArg()
{
}
public XMLArg(string XMLBufferArg)
{
this.XMLBuffer = XMLBufferArg;
}
public string XMLBufferArg
{
get
{
return this.XMLBuffer;
}
set
{
this.XMLBuffer = value;
}
}
public override string ToString()
{
return this.XMLBuffer;
}
}
clsComPlusLib is typical COM+ component contained declared event that fire
every loop through for statement inside ProcessData
method.
[ProgId("clsComPlusLib"),
Transaction(TransactionOption.NotSupported),
MustRunInClientContextAttribute(false),
EventTrackingEnabledAttribute(true),
JustInTimeActivation(true),
Synchronization(SynchronizationOption.Required),
Serializable
]
public class clsComPlusLib : ServicedComponent
{
public new event EventHandler DataArrive;
public clsComPlusLib()
{
}
public void ProcessData()
{
for (int i=0 ; i<100000 ; i++)
{
EventArgs XML = new XMLArg ("i=" + i + "<br>\n");
this.DataArrive(null,XML);
}
}
}
ComPlusLib will be register in COM+ by CLR you just need to register it in
GAC by drag and drop or gacutil.exe.
ComPlusFacade is the interesting part in this sample. ComPlusFacade is the
glue between WEB application and COM+ server application. FacadeObjByRef also
decorated with Serialization to enable transfer the class over Remoting. To
enable FacadeObjByRef to serve as Remoting server it derived from
MarshalByRefObject
and declared TcpChannel
type member.
[Serializable()]
public class FacadeObjByRef : MarshalByRefObject
{
protected ComPlusLib.clsComPlusLib eventHandler =
new ComPlusLib.clsComPlusLib();
protected System.IO.Stream outStream;
public static TcpChannel theChannel;
Class contractor get Stream as a parameter. The send stream should be page
response output stream, that will be use to write received data to the client.
Inside the constructor I register TcpChannel
to let the remoting runtime choose
an available port and set event handler to COM+ component event.
public FacadeObjByRef(System.IO.Stream outStream)
{
lock(this)
{
if (theChannel == null)
{
theChannel = new TcpChannel(0);
ChannelServices.RegisterChannel(theChannel);
}
}
if (textEncoder == null)
{
textEncoder = Encoding.UTF8;
}
this.outStream = outStream;
this.eventHandler.DataArrive += new
System.EventHandler(this.DataArive);
}
ProcessData is the class public method that will be called by WEB page and
calls COM+ application synchronously to start its work.
public void proccessData()
{
eventHandler.ProcessData();
this.CloseStream();
this.cleanUp();
}
While COM+ application does its work an event is raising and handling by
DataArrive. DataArrive converts event argument to byte array and send that byte
array to the client using internal member that holds calling page response
stream buffer.
public void DataArive(object sender, System.EventArgs e)
{
byte[] toWrite = textEncoder.
GetBytes(((ComPlusLib.XMLArg)e).XMLBufferArg + "\n");
outStream.Write(toWrite, 0, toWrite.Length);
}
ComPlusFacade run under web application process you just need to register it
in GAC.
ASPNET_COMPLUSE_CALLBACK web application got just one aspx page
(webform1.aspx) that disable default buffering behavior and create object from
FacadeObjByRef
class and call ProcessData
method.
private void Page_Load(object sender, System.EventArgs e)
{
Response.BufferOutput = false;
ComPlusFacade.FacadeObjByRef oObj = new
ComPlusFacade.FacadeObjByRef (Response.OutputStream );
oObj.proccessData ();
}
What left to be done us to run ASPNET_COMPLUSE_CALLBACK and to see its
behavior. As I explain the page is quickly up showing chunks of data that keeps
arriving and adds to the browser HTML.
Conclusion
This article aim is to show how you can use COM+ services to solve common
programming tasks while developing web applications. We start by seeing the
impact of COM+ server and library application on performance and what are the
limitations derived from server application usage.
After understanding performance consequences I try to explain and show what
is the tremendous contribute of using COM+ server application to applications
stability, robust and monitoring. By solving daily task such as long component
initialization, long components task, notification and other we see how we can
use COM+ services and especially COM+ server services save code writing and
programmers efforts.
COM+ can supply powerful services that can help you to create quickly
sophisticated and stable application. The main drawback of using COM+ services
is performance penalty. We saw that we can use part of COM+ services with
Services without Components without penalty and COM+ library application with
acceptable penalty. COM+ server that provides most of the interesting services
has bad influence over performance mainly due to its usage of DCOM.
Before using COM+ services its better to create concept application and to
test it with ACT to see if using COM+ server will fit application performance
goals. I hope that next versions of window such as Longhorn will ship with COM+
services that won�t be based on DCOM and will enable more applications to use
COM+ server application.
Previous Parts