|
Introduction
Tracking Services, as the name implies, allows us to track different activities that are related to an object's life cycle. Since this service is part of the .NET Remoting framework, it would be good to refresh our .NET Remoting concepts.
download article source code
.NET Remoting
Microsoft's .NET framework provides a way for a client in one application domain to access objects in another application domain; this is made possible through .NET Remoting.
.NET Remoting is a very flexible technology that allows clients and servers to communicate with each other, no matter where they are located. The client and server can be on the same machine, on different machines on the same network, or in different countries across the world. .NET Remoting makes life easy by providing services and infrastructure components that hide all the complexities from application developers.
.NET Remoting allows us to use different protocols, different serialization formats, configuration settings, etc. .NET Remoting even allows programmers to create and install their own "hooks" in the .NET Remoting pipeline in order to customize the default behavior of the remoting framework (Tracking Services is an example of such a hook)
So how does .NET Remoting work? Well, some of the concepts behind .NET Remoting are borrowed from the old distributed technologies, such as DCOM, CORBA, etc. First of all, always remember that in .NET Remoting, objects are exposed to the outside world through some "Listener" process; that listener process could be a console application, a WinForm application, a Windows service, or even IIS. The listener process specifies the objects that are exposed programmatically or by using the .config file. Once that part is done, client applications connect to the listener process for object creation requests and the listener process then creates and returns the objects back to the client. All communication between client and server application domains is performed by sending and receiving encoded streams of data over some channel.
Channels
The basic purpose of channels is to transport data from one end point to another; .NET comes with two built-in channels, HTTP channel (System.Runtime.Remoting.Channels.Http) and TCP channel (System.Runtime.Remoting.Channels.Tcp).
TCP channel is faster than HTTP channel, and it is good for binary data; whereas HTTP channel is very good when it comes to firewalls and the Internet. You are not limited to these channels; you can create your own custom channels and plug them into .NET Remoting.
Formatters
Formatters, as the name implies, format data or messages for delivery. Once data/message has been formatted, it is transported to other application domains by using the appropriate channel. .NET comes with two built-in formatters, Soap formatter (System.Runtime.Serialization.Formatters.Soap) and Binary formatter (System.Runtime.Serialization.Formatters.Binary).
Soap formatter formats message in Soap 1.1 specification, whereas Binary formatter formats message in pure binary form.
Types of Remoting Objects
There are two types of objects supported by .NET Remoting: server-activated objects (also known as well-known objects) and client-activated objects.
Server-activated objects
Server-activated objects, as the name implies, are created by the server and their lifetime is also managed by the server. The point to catch here is that these objects are not created when a client calls New or Activator.GetObject; rather, the actual instance of the object is created when the client actually invokes a method on proxy.
There is one implication to the above mentioned behavior. Since the object is not created at the time the client calls the New or Activator.GetObject method, we cannot use non-default constructors with server-activated objects. Only default constructors (constructors with no parameters) are supported for such objects.
There are two modes in which server-activated objects can be activated:
- Singleton
Only one object will be created on the server to fulfill the requests of all the clients; that means the object is shared, and the state will be shared by all the clients.
- SingleCall
Such objects are created on each method call and objects are not shared among clients. State should not be maintained in such objects because such objects are destroyed after each method call.
Client-activated objects
Client-activated objects are created by the server and their lifetime is managed by the client. In contrast to server-activated objects, client-activated objects are created as soon as the client calls New or any other object creation method; therefore, we can use both the default and non-default constructors with client-activated objects. Client-activated objects are specific to the client, and objects are not shared among different clients; object instance exists until the lease expires or the client destroys the object.
Configuration
Like other .NET technologies, .NET Remoting also needs some configuration information in order to work. We can configure our remoting application either programmatically or by using a .config file. For this article, we will discuss .config files.
.NET Remoting configuration settings are packed in the <application> tag in the following XML format:
<configuration>
<System.Runtime.Remoting>
<application>
</application>
</System.Runtime.Remoting>
</configuration>
In order to expose server-activated objects, we use the <service> tag, as shown in the example below:
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall" type="MyObjects.Customer, MyObjects"
objectUri="Customer"/>
</service>
<channels>
<channel ref="tcp" port="1234"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
The above configuration specifies that we are exposing a SingleCall object whose complete type name is MyObjects.Customer. The class is contained in the MyObjects.DLL assembly; the object's URI is Customer, and the object is accessible through TCP channel on port 1234.
Each server-activated object will have its own <wellknown> tag.
The corresponding client-side .config file would be as follows:
<configuration>
<system.runtime.remoting>
<application name="MyClient">
<client>
<wellknown type="MyObjects.Customer,MyObjects"
url="tcp://localhost:1234/Customer"/>
</client>
<channels>
<channel ref="tcp"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
The client-side configuration file is slightly different from the server configuration file. We are using the <client> tag instead of the <service> tag; similarly we are using the url attribute in the <wellknown> tag on the client-side to specify the exact location of the server object.
For client-activated objects, we use the <activated> tag (both on sever and the client), instead of the <wellknown> tag. Following example shows how to expose a client-activated object.
<system.runtime.remoting>
<application name="Data">
<service>
<activated type="MyObjects.Customer,MyObjects"/>
</service>
<channels>
<channel ref="tcp" port="1234"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
The corresponding client-side configuration file would look as follows:
<configuration>
<system.runtime.remoting>
<application name="Data">
<client url="tcp://localhost:1234/Data">
<!-- You can only use one url @ a time -->
<activated type="MyObjects.Customer,MyObjects"/>
</client>
<channels>
<channel ref="tcp"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
.NET Tracking Service
.NET Tracking Service provides a way to plug our own code in the .NET Remoting infrastructure in order to receive the notifications about object marshaling. For example, the .NET Tracking Service will raise an event when an object gets marshaled to another application domain, etc. .NET Tracking Service raises some events that are related to the marshaling process of Marshal-by-Ref (MBR) objects. A component that is interested in these events can register with the .NET Remoting infrastructure in order to receive those events.
.NET Tracking Service lends us the flexibility to monitor and analyze a running application and observe the application patterns in order to see how our application is behaving under certain workloads, how many objects are getting created during peak time, etc. .NET Tracking Service provides us with lots of helpful information during those events; we can log that information for trend analysis and other observations.
There is only one restriction with .NET Tracking: It works for Client-activated objects only!
Like other .NET pluggable architectures, creating a component that utilizes the .NET Tracking Service is very easy. There are two simple steps that we have to follow:
- Create a .NET class that implements the ItrackingHandler interface (we'll call such a class Tracking Handler)
- Register that class with the .NET Remoting infrastructure by using the TrackingServices class.
I'll explain in detail.
ITrackingHandler Interface
ItrackingHandler interface is part of System.Runtime.Remoting.Services namespace and any class that implements this interface can be used to receive object marshaling events. This interface has following methods:
|
Method
|
Description
|
|
MarshaledObject
|
This method is called to notify tracking handlers that an
object is being marshaled.
|
|
UnmarshaledObject
|
This method is called to notify tracking handlers that an
object is being unmarshaled.
|
|
DisconnectedObject
|
This method is called to notify tracking handlers that an
object is being disconnected (for example object lease has been expired)
|
Implement this interface in your class, as shown below:
Public class MyTrackingHandler : ITrackingHandler
{
public void MarshaledObject(object obj, ObjRef or)
{
// Write your code here; most probably you would log the
// information
}
public void UnmarshaledObject(object obj, ObjRef or)
{
// Write your code here; most probably you would log the
// information
}
public void DisconnectedObject(object obj)
{
// Write your code here; most probably you would log the
// information
}
}
You can see from the code that both the MarshaledObject and UnmarshaledObject methods receive two parameters: an object and an ObjRef; whereas DisconnectedObject receives only one parameter: an object. All object parameters contain a reference to the object that is being marshaled, unmarshaled, or disconnected; whereas all ObjRef contain a reference to an object that contains all the information that is required to generate a proxy in the client application domain. The ObjRef contains information such as the Type of the server object, object's location on the server, and other very important connection and transport related information critical for the proxy.
TrackingServices Helper Class
TrackingServices is a helper class that is part of the System.Runtime.Remoting.Services namespace; this class is used to manipulate the tracking handlers. Following are the important methods of this class:
|
Method
|
Description
|
|
RegisterTrackingHandler
|
Adds a tracking handler in the list of handlers that are interested
in receiving the object marshaling events.
|
|
UnregisterTrackingHandler
|
Removes a tracking handler from the list of handlers that
are interested in receiving the object marshaling events.
|
|
RegisteredHandlers
|
Returns an array of ITrackingHandler type that contains
references to the currently registered tracking handlers.
|
Following code shows how to register our own tracking handler with the .NET Remoting infrastructure:
MyTrackingHandler objHandler = new MyTrackingHandler() ;
TrackingServices.RegisterTrackingHandler(objHandler) ;
Once our handler is registered, we will be receiving the events. In order to stop receiving events, we would have to call UnregisterTrackingHandler method, as shown below (assuming that objHandler points to a previously registered tracking handler):
TrackingServices.UnregisterTrackingHandler(objHandler) ;
Enumerating through all the registered tracking handlers is also easy. Following code gets the list of currently registered tracking handlers and iterates through them:
ITrackingHandler[] objHandlers = TrackingServices.RegisteredHandlers ;
foreach (ITrackingHandler objHandler in objHandlers)
{
// Use objHandler here
}
Let's Do Something Practical
Now that we are familiar with all the background information we need for building our own custom tracking handler, it is time to write one.
The custom tracking handler that we are going to build will log all the information in a SQL Server database table. This will allow us to analyze the data and observe the object creation and marshaling behavior of our application. Some of the things that we can analyze from such data are:
- Number of objects that were being marshaled, unmarshaled, or disconnected during a period of time
- State of each marshaled, unmarshaled, and disconnected object
- The URI that was used to instantiate the object
These are the projects in the sample code:
- MyObjects
- TrackingHandlers
- TrackingListener
- TrackingClient
Apart from the above projects, there is one database.sql file that contains SQL script for creating an SQL server database, database table, and stored-procedure.
MyObjects.DLL
MyObjects assembly contains a class named Customer that will be exposed to the remoting clients through TrackingListener.exe.
Customer is a simple C# class that overrides the default ToString method so that we can get our hands on the internal state of the object. We could have marked the object as serializable by using the [serializable] attribute or could have implemented the Iserializable interface in order to get the state of the object, but I opted for the ToString method.
The following is the code of the Customer class.
public class Customer : MarshalByRefObject
{
private string mstrName ;
private string mstrAddress ;
private string mstrPhone ;
private DateTime mdtDOB ;
public Customer()
{
//
// TODO: Add constructor logic here
//
mstrName = "Mansoor Siddiqui" ;
mstrAddress = "9999 Street, Apt 9, City State, USA" ;
mstrPhone = "999-999-9999" ;
mdtDOB = DateTime.Parse("01/01/1980") ;
}
public override string ToString()
{
string strState ;
strState = Name + "|" + Address + "|" + Phone + "|" +
DOB.ToLongDateString() ;
return strState;
}
// Rest of the code has been skipped due to length
}
TrackingHandlers.DLL
TrackingHandlers assembly contains our custom tracking handler named MyTrackingHandler. As explained above, we have to implement the ItrackingHandler interface in order to make our class a tracking handler.
The following is the code of our custom tracking handler.
public class MyTrackingHandler : ITrackingHandler
{
private Database mobjDatabase ;
private string mstrConnectionString ;
public MyTrackingHandler()
{
//
// TODO: Add constructor logic here
//
mobjDatabase = new Database() ;
mstrConnectionString = "Data Source=localhost;uid=sa;pwd=;Initial
Catalog=TrackingDB" ;
}
public void MarshaledObject(object obj, ObjRef or)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "M" ;
FillParameters(arrobjParameters, obj, or) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters) ;
}
public void DisconnectedObject(object obj)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "D" ;
FillParameters(arrobjParameters, obj, null) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters) ;
}
public void UnmarshaledObject(object obj, ObjRef or)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "U" ;
FillParameters(arrobjParameters, obj, or) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters) ;
}
private void FillParameters(object[] r_objArray, object obj,
ObjRef or)
{
r_objArray[1] = DateTime.Now ;
r_objArray[2] = obj.ToString() ;
r_objArray[3] = obj.GetHashCode() ;
if (or != null)
{
if (or.TypeInfo != null)
{
r_objArray[4] = or.TypeInfo.ToString() ;
r_objArray[5] = or.TypeInfo.TypeName ;
}
else
{
r_objArray[4] = "" ;
r_objArray[5] = "" ;
}
if (or.URI != null)
r_objArray[6] = or.URI.ToString() ;
else
r_objArray[6] = "" ;
}
else
{
r_objArray[4] = "" ;
r_objArray[5] = "" ;
r_objArray[6] = "" ;
}
}
The code in MarshaledObject, UnmarshaledObject and DisconnectedObject methods simply save the information into database.
TrackingListener.exe
TrackingListener.exe is the server-side listener, which creates and installs the tracking handler in the .NET Remoting pipeline and fulfills the object creation requests.
The following is the TrackingListener class code.
class TrackingListener
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("TrackingListener.exe.config") ;
TrackingServices.RegisterTrackingHandler(new
TrackingHandlers.MyTrackingHandler()) ;
Console.WriteLine("Press enter to terminate ...") ;
Console.ReadLine() ;
}
}
The code is very simple. RemotingConfiguration.Configure method is called by the applications that use .config files; calling this method configures the application according to the settings specified in the configuration file.
The configuration settings for our listener process are specified in TrackingListener.exe.config file. Following is the snippet from the .config file:
<configuration>
<system.runtime.remoting>
<application name="TrackingTest">
<service>
<activated type="MyObjects.Customer, MyObjects"/>
</service>
<channels>
<channel ref="tcp" port="9000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
You can see that the exposed object is client-activated, and it is accessible through TCP channel port 9000.
TrackingClient
TrackingClient is a WinForm application that simply creates an instance of the Customer class in order to generate some activity on the server.
Testing the Tracking Handler
In order to test the tracking handler, we have to perform the following tasks.
First of all, run the database script in order to create the database and related database objects. This article assumes that SQL Server in also running on the same box as that of the listener, but if SQL Server is on another machine, then you would have to change the connection string in the tracking handler class.
A better solution would be to store the connection string in the .config file of the listener process and the listener should pass that string to handler via constructor.
Run the listener process, TrackingListener.exe, from the directory where you unzipped the sample code; the listener process will register appropriate channels and a console window will be opened. Now our Customer object is ready to be created.
Launch TrackingClient.exe and press the Create object button on the dialog box; this will instantiate a Customer instance and will cause the .NET Remoting infrastructure to call our tracking handler's MarshaledObject method. Once the object is created successfully, go and look at the TrackingLog table in the database. On my machine, I had the following rows in the database table:
In order to test the disconnect event, just wait 5 minutes (because 5 minutes is the default lease time for client-activated objects, after that objects are disconnected from the proxy). On my machine,the following rows were in the database:
Note that I am storing "M", "U", or "D" in the activity column of the database table; these values represent the activity (Marshaled, Unmarshaled, or Disconnect) that occurred.
Conclusion
.NET framework and all of its related technologies allow us to plug our own hooks in their processing pipeline; .NET tracking handlers is an example of such a hook. As you have seen, it is very easy to write tracking handlers, but we should have background information about .NET Remoting, different object types, configuration files, and other remoting intricacies.
About the Author
Mansoor Ahmed Siddiqui is a software consultant and technical writer working in the United States. He has a masters degree in computer science and been involved in software development since 1997. His areas of expertise include designing and developing Web-based applications, client-server applications, and n-tier applications, with a special focus on middle-tier and Win32 programming.
He has expertise in Microsoft. NET Framework, ASP.NET, C#, Visual Studio .NET, Web Services, ADO.NET, ASP, JavaScript, eXtensible Markup Language (XML), Simple Object Access Protocol (SOAP), Visual C++, Microsoft Foundation Class Library (MFC), Active Template Library (ATL), Visual Basic 6.0, ActiveX Data Objects (ADO), COM/DCOM/COM+, Microsoft Transaction Server (MTS), Microsoft Message Queue (MSMQ), SQL Server 7.0/2000, OMG's Unified Modeling Language (UML), Rational Software Corp.'s Rational Rose, Java, Java Server Page (JSP), servlets, Enterprise JavaBeans (EJBs), Java 2 Platform Enterprise Edition (J2EE).
Currently, he is working with Visual Studio 7.0 and the Microsoft .NET platform. He is an MCSD and is Brainbench certified in different languages.
Apart from technical writing, other interests include listening to music, swimming, playing cricket and pool, and hanging out with friends. He can be reached at mansoorasiddiqui@hotmail.com and ICQ 151707288.
|