Using the Remoting Callbacks in .Net Applications.






3.45/5 (18 votes)
Nov 18, 2001
11 min read

237868

2953
This article describes how to implement an asynchronous remoting callbacks from different remote objects such as .Net Service (COM+), Web Service and classic .Net object using the C# language.
Contents
- Introduction
- Concept and Design
- Remoting Interface
- Callback Custom Attribute
- Remoting Method
- Implementation
- RemoteCallbackLib
- RemoteObject (A , X and WS)
- RemoteObjectX
- RemoteObjectWS
- HostServer
- HostServer.exe.config
- RemoteWindowsForm
- Initialization
- Callback object
- Invoking the Remote Method
- RemoteWindowsForm.exe.config
- Test
- Conclusion
Introduction
The object remoting is an integral part of the .Net Framework. It allows to call
an object located on the Enterprise network using the same programming techniques
like access to object located within the application domain. The remoting is a peer-to-peer
communication mechanism between the remote object (server) and its consumer on the
client side. The objects can be hosted in different application domains such as
Web Server, Web Service, COM+ Service, etc. The remoting call is processing
in a synchronously manner with the option of the asynchronously end notification
on the client side. This design pattern is knew as a BeginInvoke/EndInvoke
technique.
The client will receive a notification of the end remoting with the return value
of the called method. Note that the client has no state of the remote method during
this remoting process. (only finally as a returned value). This article will describe
a mechanism of the remoting callback which can be used to notify client about the
remote method state. This solution is suitable for any cases where "real-time"
processing is required. In my simple sample I will demonstrate how to make a Windows
Form more sensitive and pre-emptive during the remoting time using the Remote
Callbacks.
Concept and Design
The concept of the remoting call-back is based on the remoting delegate, where a callback object is delegated to the remote object within the invoked method. The following picture shows the remoting callback "plumbing" mechanism:
The remoting part is sitting between the client and server objects. From the application point of view it represents by the proxy/stub of the remoting object, communication channel and message format protocol. The client/server connectivity is started at the remote object with publishing its metadata (url object) and registering the stub object in the specified listener such as tcp or http channel and port. This process can be done either programmatically or administratively using a config file. The client side needs to know where the remote object metadata located and which channel and port is dedicated for this connectivity (listener channel). Based on this information, the client will create a proxy object of the remote object within its application domain. Also like a server side, this process can be completed by either the hard-coding or using the config file. Using config files to establish a "wiring" between the client/server is more flexible solution than hard-coding.
The above picture shows two remoting objects between the client and sever side:
- Remote object - is calling by the client (Windows Form) to invoke any public member of the remote object. The object can be setup for any mode such as Singleton, SingleCall or Client Activated. Note that remoting callback mechanism doesn't need to keep an object state (for instance: event class), it's a stateless - loosely coupled design pattern. The connectivity are setup explicitly and transparently (the remote object has to be published).
- Callback object - is calling by the remote object within the method scope. Here is a situation little bit different. The remote callback is a private client-server contract and it is configured implicitly. The major different is that the client creating the callback proxy and then it is delegated to the remote object as a method argument. The callback object is running in the Singleton mode with unlimited lease time.
Based on the physical connectivity with the remoting objects, the client can create one common callback object for the same channel and formatter for different remote objects. This sample using a common callback object, where Windows Form calls concurrently three different remote objects and passing delegate of the same callback proxy. Note that each remote object will create own remote callback proxy based on the delegate metadata and using the Reflection design pattern.
The following picture shows a situation on the client side using multiple remoting objects:

This is a generic model for any number of the remoting callback objects. The callback object is receiving a state from the remote object and passing it to the thread pool in the "fire&forget" fashion. The worker thread then dispatching a call to the Windows Control handler based on the cookie and senderId value.
Note that every object is serialized/de-serialized between the proxy/stub, therefore it has to be derived from the ISerializable (or attributed).
Let's look at closely for some issues which are related with the design implementation:
Remoting Interface
Interface is a contract between the object and its consumer. The interfaces
allow to publish an object based on their abstract definitions. This
encapsulation is giving a more flexibility in the design implementation. The
major advantage using interfaces (or abstract class) is their loosely design pattern,
which it may play an important role in the remoting issue. It is a good design
technique to put all common abstract definitions into one assembly. In my
sample I created separate project called a ContractObject
for that issue.
The following is an abstract definition of the remote object contract:
namespace RKiss.RemoteObject { // stateless delegate (it can be any generic signature) public delegate bool RemoteCallback(string sender, object e); // interface [Guid("B19A2AD2-31F2-4c6e-B5A6-24495670BE02")] public interface IRmObject { string GiveMeCallback(int timeinsec, string ticketId, object wire); string Echo(string msg); } // Callback EventArgs class [Serializable] public class CallbackEventArgs : EventArgs { private string _sender; private string _ticketId; private object _state; public string Sender { get { return _sender; } set { _sender = value; } } public string TicketId { get { return _ticketId; } set { _ticketId = value; } } public object State { get { return _state; } set { _state = value; } } } }
There are three parts of the metadata in its assembly: delegator, interface
and callback's EventArg
class. They can be modified based on needs of the
application. Note that interface has been attributed by Guid value to keep its
ID the same (The .Net Service will be accepted this Guid each time when object is going to
be re-registered into the COM+ catalog)
Callback Custom Attribute
For setup a config of the Remote Callback object on the client side is
suitable to use a custom attribute technique. It will allow to hide and reuse
all implementation for this private remoting object. I created a separate project
- RemoteCallbackLib
to handle this solution.
There is a RemoteCallbackAttribute
to config
any attributed object (of course derived from the
MarshalByRefObject
class) for the Remoting purpose.
The following code snippet shows its usage:
[RemoteCallback("tcp", desc="Callbacks Test")] public CallbackClass cb = null; // callback Admin private RemoteCallbackSubscriber sub = null;
The RemoteCallback attribute is activated by its subscriber - RemoteCallbackSubscriber
, which has to
be constructed during the client's initialization:
sub = new RemoteCallbackSubscriber(this); cb.Parent = this; // we need an access to the parent properties
Remoting Method
The method signature of the Remoting object which wants to use the Remoting Callback mechanism includes less two additional arguments such as the ticketId and objwire as it is shown in the following snippet:
public string GiveMeCallback(int timeinsec, string ticketId, object objwire)
The ticketId
represents a cookie value to handle
multiple callbacks on the client side. The objwire
is a delegate object of the client's callback proxy.
Implementation
The sample solution of the Remote Callbacks implemenation is divided into several projects:
- ContractObject - common abstract definitions (see the above)
- RemoteCallbackLib - RemoteCallback Custom Attribute (see the above)
- RemoteObjectA - classic .Net object hosted in the HostServer
- RemoteObjectX - .Net Service (COM+) hosted in the HostServer
- RemoteObjectWS - Web Service hosted in the IIS
- HostServer - console server program
- RemoteWindowsForm - Windows Form client program
also the solution included the following config files:
- HostServer.exe.config - the Remoting config info for objects hosted by HostServer
- RemoteWindowsForm.exe.config - the config info for Remoting objects using by this client
RemoteCallbackLib
The following code snippet shows the implementation of the RemoteCallbackAttribute
. Its design is based on the Refection of the "parent"
assembly, where located all metadata of the Callback object. The
RemoteCallbackLib
is built into the separate assembly which it can be reused by another remoting clients.
namespace RKiss.RemoteCallbackLib { [AttributeUsage(AttributeTargets.Field)] public class RemoteCallbackAttribute : Attribute { private string _desc; private string _protocol; private int _port; public string desc { get { return _desc; } set {_desc = value; } } public string protocol { get { return _protocol; } set {_protocol = value; } } public int port { get { return _port; } set {_port = value; } } public RemoteCallbackAttribute() : this("tcp", 0) {} public RemoteCallbackAttribute(string sProtocol) : this(sProtocol, 0) {} public RemoteCallbackAttribute(string sProtocol, int iPort) { protocol = sProtocol; port = iPort; } } // Creating remoting stuff based on the properties of the CallbackAttribute // Note that all callback class' have to located in the same (parent) assembly! public class RemoteCallbackSubscriber { private Hashtable HT = Hashtable.Synchronized(new Hashtable()); private string site = "localhost"; public RemoteCallbackSubscriber(object parent) { Type typeParent = parent.GetType(); foreach(FieldInfo fi in typeParent.GetFields()) { foreach(Attribute attr in fi.GetCustomAttributes(true)) { if(attr is RemoteCallbackAttribute) { RemoteCallbackAttribute rca = attr as RemoteCallbackAttribute; // open in/out channel int port = rca.port; if(port == 0) { Random rdmPort = new Random(~unchecked((int)DateTime.Now.Ticks)); port = rdmPort.Next(1, ushort.MaxValue); } // create channel IChannel channel = null; if(rca.protocol == "tcp") channel = new TcpChannel(port); else if(rca.protocol == "http") channel = new HttpChannel(port); else throw new Exception(string.Format("The '{0}' is not recognize protocol.", rca.protocol)); // register channel ChannelServices.RegisterChannel(channel); // register a Callback Object string nameofCallbackObject = fi.FieldType.FullName; Type typeofCallbackObject = typeParent.Assembly.GetType(nameofCallbackObject); RemotingConfiguration.RegisterWellKnownServiceType( typeofCallbackObject, nameofCallbackObject, WellKnownObjectMode.Singleton); // create proxy string urlofCallbackObject = string.Format(@"{0}://{1}:{2}/{3}", rca.protocol, site, port, nameofCallbackObject); object proxyCB = Activator.GetObject(typeofCallbackObject, urlofCallbackObject); fi.SetValue(parent, proxyCB); // //store into the HT HT[fi.Name] = channel; } } } } public IChannel this [string nameofCallback] { get { return HT[nameofCallback] as IChannel; } } } }
RemoteObject (A , X and WS)
The design pattern of the Remoting methods is the same for any of these
remoting objects. The following code snippet shows the RemoteObjectA
class derived from the
MarshalByRefObject
class and IRmObject
interface.
using RKiss.RemoteObject; // abstract definitions namespace RKiss.RemoteObjectA { public class RmObjectA : MarshalByRefObject, IRmObject { public RmObjectA() { Trace.WriteLine(string.Format("[{0}]RmObjectA activated", GetHashCode())); } public string Echo(string msg) { Trace.WriteLine(string.Format("[{0}]RmObjectA.Echo({1})", GetHashCode(), msg)); return msg; } // stateless callbacks public string GiveMeCallback(int timeinsec, string ticketId, object objwire) { RemoteCallback wire = objwire as RemoteCallback; bool bProgress = true; CallbackEventArgs cea = new CallbackEventArgs(); cea.TicketId = ticketId; cea.State = "running ..."; cea.Sender = GetType().ToString(); wire(cea.Sender, cea); while(timeinsec-- > 0 && bProgress) { Thread.Sleep(1000); cea.State = timeinsec; bProgress = wire(cea.Sender, cea); } cea.State = bProgress?"done":string.Format("aborted at {0}", ++timeinsec); wire(cea.Sender, cea); // Trace.WriteLine(string.Format("[{0}]RmObjectA.GiveMeCallback({1}, {2}, {3})", GetHashCode(), timeinsec, ticketId, wire.GetType().FullName)); return ticketId; } } }
There are implementation of two methods of the
IRmObject
interface in the object. The first one -
Echo has a test purpose, the other one -
GiveMeCallback
simulated some time consuming work with notification of
the
method state using the Remoting Callback mechanism. The method state is wrapped
and serialized by the CallbackEventArgs
object.
Now I will show you only differencies in the following remote objects:
RemoteObjectX
This is a .Net Service - transactional and poolable object register into the
COM+ catalog and config as a remoting object. There are attributes for assembly
and class necessary for this object configuration in the COM+ catalog. The other
change, the remote class is
derived from the ServicedComponent
. This is a standard stuff for the .Net Service
configuration.
using RKiss.RemoteObject; // abstract definitions [assembly: ApplicationName("RemoteObjectX")] [assembly: ApplicationID("026B9E80-6B07-45f0-8EBF-BD35B5D3BB77")] [assembly: Description("Remoting COM+ test")] [assembly: ApplicationActivation(ActivationOption.Library)] // accepted by Beta2 [assembly: ApplicationAccessControl(Value = false, Authentication = AuthenticationOption.None)] namespace RKiss.RemoteObjectX { [Guid("B19A2AD2-31F2-4c6e-B5A6-24495670BE02")] [Description("Remoting Object")] [ObjectPooling(Enabled=true,MinPoolSize=3,MaxPoolSize=64)] [Transaction(TransactionOption.Required)] [ConstructionEnabled(Default="server=ATZ-ROMAN;uid=sa;pwd=;database=Logger")] [EventTrackingEnabled] public class RmObjectX : ServicedComponent, IRmObject { ... } }
Note that object has to run in the COM+ library only, that's the limitation of the Beta2 and it will be fix it in the RTM version.
RemoteObjectWS
This is a Web Service object generated by wizard and modify for the remoting purpose. The following code snippet shows its boilerplate:
using RKiss.RemoteObject; // abstract definitions namespace RemoteObjectWS { public class RmObjectWS : MarshalByRefObject, IRmObject { public RmObjectWS() { ... } #region Component Designer generated code ... #endregion [WebMethod] public string Echo(string msg) { ... } // stateless callbacks [WebMethod] public string GiveMeCallback(int timeinsec, string ticketId, object objwire) { ... } } }
Note that this project has to be unzip and move it into the localhost\RemoteObjectWS
directory in prior of opening the solution.
HostServer
This is a server program to host a remote object. It's a very simply console program to perform a configuration of the remote object(s). There are two kinds of options for this configuration as I mentioned earlier. The option 1 is commented.
namespace RKiss.ServerActivation { public class Server { public static int Main(string [] args) { try { /* option 1 TcpChannel chan = new TcpChannel(12345); ChannelServices.RegisterChannel(chan); RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("RKiss.RemoteObjectA.RmObjectA, RemoteObjectA"), @"RemoteTest/ObjectA", WellKnownObjectMode.SingleCall); RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("RKiss.RemoteObjectX.RmObjectX, RemoteObjectX"), @"RemoteTest/ObjectX", WellKnownObjectMode.Singleton); */ // option 2 RemotingConfiguration.Configure(@"..\..\HostServer.exe.config"); } catch(Exception ex) { System.Console.WriteLine(ex.Message); } System.Console.WriteLine("Hit <enter> to exit..."); System.Console.ReadLine(); return 0; } } }
HostServer.exe.config
This is
a configuration file to config the Remoting ObjectA
and ObjectX
on the Tcp
channel port# 12345. Both objects have been choose for server activatation
running as a wellknown object in the SingleCall
mode. This config file can be modified during the
deploying process to match an application environment.
<configuration>
<system.runtime.remoting>
<application name="RemoteTest">
<service>
<wellknown mode="SingleCall"
type="RKiss.RemoteObjectA.RmObjectA, RemoteObjectA"
objectUri="ObjectA" />
</service>
<service>
<wellknown mode="SingleCall"
type="RKiss.RemoteObjectX.RmObjectX, RemoteObjectX"
objectUri="ObjectX" />
</service>
<channels>
<channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting"
port="12345" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
RemoteWindowsForm
This is a client side - consumer of the Remoting objects. The Remoting client can be any object on the Enterprise Network. This sample is using a Windows Form to demonstrate an asynchronously invoking remote objects with callbacks to the Windows Controls.
There are 3 major parts on the client side related to the remoting callbacks:
Initialization
The Initialization part has the following responsibility for the remoting process and connectivity:
- subscribing (config) a callback object as a remoting object
- creating proxy objects of the requested remote objects based on the configuration file
- making an Echo test on the remoting objects
The following code snippet shows that:
public Form1() { InitializeComponent(); try { sub = new RemoteCallbackSubscriber(this); cb.Parent = this; // we need an access to the parent properties // get the remoting uriAddress NameValueCollection uriAddr = (NameValueCollection)ConfigurationSettings.GetConfig("Client/urlAddress"); string uriObjectA = (string)uriAddr["objectA"]; string uriObjectX = (string)uriAddr["objectX"]; string uriObjectWS = (string)uriAddr["objectWS"]; // type of the Remote Interface Type typeofRI = typeof(RKiss.RemoteObject.IRmObject); // create a proxy of the remote objects roA = (IRmObject)Activator.GetObject(typeofRI, uriObjectA); roX = (IRmObject)Activator.GetObject(typeofRI, uriObjectX); roWS = (IRmObject)Activator.GetObject(typeofRI, uriObjectWS); // // (sub["cb"] as TcpChannel).StartListening(0); // sample how to control a Tcp channel // echo test textBoxStatus.Text = roWS.Echo(roA.Echo(roX.Echo("This is an ECHO Message"))); } catch(Exception ex) { textBoxStatus.Text = ex.Message; buttonRM.Hide(); } }
Callback object
The callback object is the remoting object for the client's remoting objects,
that's why it is derived from the MarshalByRefObject
class. The callback object
has been initiated for infinity lease time (actually its life time is depended
from the client's life time). There is one callback method - Progress with the
arguments (state) passed from the remote object. Based on this state, the DispatchEvent
helper calls a particular Windows Control. Note that callback
method is running in the "fire&forget" fashion to isolated processes and
minimize its respond time. The callback object holding a state of the
progressing and passing back to the remote object which it will allow to abort
the remoting call. This a a great feature of the remoting callbacks to make the
remote call pre-emptive.
// -----------< Callback class >------------------------------------------- public class CallbackClass : MarshalByRefObject { private Form1 _parent = null; private bool _state = false; public Form1 Parent { set { _parent = value; }} public bool State { set { lock(this) { _state = value; }}} // set a leaseing time for infinity public override Object InitializeLifetimeService() { ILease lease = (ILease)base.InitializeLifetimeService(); if(lease.CurrentState == LeaseState.Initial) { lease.InitialLeaseTime = TimeSpan.FromMinutes(0); } return lease; } // // CallbackMethod public bool Progress(string sender, object e) { // fire&forget ThreadPool.QueueUserWorkItem(new WaitCallback(DispatchEvent), e); return _state; } // helper private void DispatchEvent(object e) { CallbackEventArgs cea = e as CallbackEventArgs; // a simple work with the parent's control lock(this) { if(cea.TicketId == "ProgressBar") { if(cea.State is int) { _parent.TextBoxStatus.Text = cea.State.ToString(); _parent.ProgressBarStatus.PerformStep(); } else if(cea.State is string) _parent.TextBoxStatus.Text = cea.State.ToString(); } else if(cea.TicketId == "TextBox") { if(_parent.TextBoxStatus.BackColor == Color.Yellow) _parent.TextBoxStatus.BackColor = Color.White; else _parent.TextBoxStatus.BackColor = Color.Yellow; } else if(cea.TicketId == "Button") { if(_parent.ButtonRM.ForeColor == Color.Magenta) _parent.ButtonRM.ForeColor = Color.Black; else _parent.ButtonRM.ForeColor = Color.Magenta; } } } } // end of the CallbackClass
Invoking the Remote Method
This code snippet shows invoking a method on the Remoting objects using the asynchronous design pattern. Clicking on the button Run/Abort the following code snippet is going to be perform:
private void buttonRM_Click(object sender, System.EventArgs e) { try { if(buttonRM.Text == "Run") { // client state buttonRM.Text = "Busy"; progressBarStatus.Value = 0; textBoxStatus.Text = string.Empty; cb.State = true; // // arguments int timeinsec = 10; string ticketId = "ProgressBar"; RemoteCallback wire = new RemoteCallback(cb.Progress); // // invoking method on the objectA AsyncCallback acbA = new AsyncCallback(asyncCallBackA); DelegateGiveMeCallback dA = new DelegateGiveMeCallback(roA.GiveMeCallback); IAsyncResult arA = dA.BeginInvoke(timeinsec, ticketId, wire, acbA, null); // // invoking method on the objectX AsyncCallback acbX = new AsyncCallback(asyncCallBackX); DelegateGiveMeCallback dX = new DelegateGiveMeCallback(roX.GiveMeCallback); IAsyncResult arX = dX.BeginInvoke(timeinsec, "TextBox", wire, acbX, null); // // invoking method on the objectWS AsyncCallback acbWS = new AsyncCallback(asyncCallBackWS); DelegateGiveMeCallback dWS = new DelegateGiveMeCallback(roWS.GiveMeCallback); IAsyncResult arWS = dWS.BeginInvoke(timeinsec, "Button", wire, acbWS, null); // buttonRM.Text = "Abort"; } else if(buttonRM.Text == "Abort") { cb.State = false; } } catch(Exception ex) { textBoxStatus.Text = ex.Message; buttonRM.Text = "Run"; } } // async result from the remoting objects private void asyncCallBackA(IAsyncResult ar) { DelegateGiveMeCallback d = (DelegateGiveMeCallback)((AsyncResult)ar).AsyncDelegate; object o = d.EndInvoke(ar); buttonRM.Text = "Run"; } private void asyncCallBackX(IAsyncResult ar) { DelegateGiveMeCallback d = (DelegateGiveMeCallback)((AsyncResult)ar).AsyncDelegate; object o = d.EndInvoke(ar); buttonRM.Text = "Run"; } private void asyncCallBackWS(IAsyncResult ar) { DelegateGiveMeCallback d = (DelegateGiveMeCallback)((AsyncResult)ar).AsyncDelegate; object o = d.EndInvoke(ar); buttonRM.Text = "Run"; }
The design implementation is the same for any remoting object. It's starting
to create a callback delegator - RemoteCallback
for
the specified callback method (Progress). Secondly, the
AsyncCallBack
is initiated for the particular handler function (for
instance; asyncCallBackA
). This function is going
to be called at the end of the remoting call. Next step is to create a delegator
of the Remoting method for the asynchronous call. As a last step is invoking a
delegator method using the BeginInvoke
function.
Note that all calls are processing without any blocking, so the Windows Control thread can be yielded and be ready to process any event such as callbacks, user interface, asyncCallBacks, etc. When the remoting is done, the post-call function (handler) is called to finish and resulting the remoting method.
RemoteWindowsForm.exe.config
This is a client configuration file. As you can see it's a different from the server side and also from the standard one. There is no section for the system.runtime.remoting. I created a custom section Client\urlAddress where located an objecturi address of the remote object which client wants to talk.
<configuration> <configSections> <sectionGroup name="Client"> <section name="urlAddress" type="System.Configuration.NameValueSectionHandler,System" /> </sectionGroup> </configSections> <Client> <urlAddress> <add key="objectA" value="tcp://localhost:12345/RemoteTest/ObjectA" /> <add key="objectX" value="tcp://localhost:12345/RemoteTest/ObjectX" /> <add key="objectWS" value="http://localhost/RemoteObjectWS/RemoteObjectWS.soap" /> </urlAddress> </Client> </configuration>
Test
Testing of the Remoting Callbacks solution needs to install properly Web Service and .Net Service projects. The package has included a batch files to make their installation easy. Please, look at them an change their pathname related to your setup environment if it's necessary. After that, launch the client program (RemoteWindowsForm) and you should see the following form:

The Echo Message has been nested trough all of three remote objects. This is an indication that your connectivity with the remote objects are valid. Now click the button Run to process the remoting callbacks.

As you can see, each remote object will notify its control on the Window Form such as the progress bar, text and button colors. Pressing the button Abort the remoting process will be aborted on the all remoting objects. After these simple test, lunch more clients (for instance five) and repeat the above test simultaneously for all of them. I hope everything is working well like on my machine.
Conclusion
Using the Remoting Callback in the .Net Application enhancing its event driven model, making more pre-emptive and realistic. As typically example is downloading a large file or query database. In this article I described the capability of the .Net Remoting and Reflexion which they play very significant role in the .Net Enterprise Solutions.