RESOLUTION
The following is the corrected sample code for
ChatCoordinator.cs. The common abstract class is RemotelyDelegatableObject.
Note that the client application will need to define a class that derives from
RemotelyDelegatableObject and implements its callback logic there.
// ChatCoordinator.cs
using System;
using System.Runtime.Remoting;
using System.Collections;
// Define the class that contains the information for a Submission event.
[Serializable]
public class SubmitEventArgs : EventArgs
{
private string _string = null;
private string _alias = null;
public SubmitEventArgs(string contribution, string contributor)
{
this._string = contribution;
this._alias = contributor;
}
public string Contribution
{
get
{
return _string;
}
}
public string Contributor
{
get
{
return _alias;
}
}
}
// The delegate declares the structure of the method that the event will call when it occurs.
// Clients implement a method with this structure, create a delegate that wraps it, and then
// pass that delegate to the event. The runtime implements events as a pair of methods,
// add_Submission and remove_Submission, and both take an instance of this type of delegate
// (which really means a reference to the method on the client that the event will call).
public delegate void SubmissionEventHandler(object sender, SubmitEventArgs submitArgs);
// Define the service.
public class ChatCoordinator : MarshalByRefObject
{
public ChatCoordinator()
{
Console.WriteLine("ChatCoordinator created. Instance: " + this.GetHashCode().ToString());
}
// This is to insure that when created as a Singleton, the first instance never dies,
// regardless of the time between chat users.
public override object InitializeLifetimeService()
{
return null;
}
// The client will subscribe and unsubscribe to this event.
public event SubmissionEventHandler Submission;
// Method called remotely by any client. This simple chat server merely forwards
// all messages to any clients that are listening to the Submission event, including
// whoever made the contribution.
public void Submit(string contribution, string contributor)
{
Console.WriteLine("{0} sent: {1}.", contributor, contribution);
// Package String in SubmitEventArgs, which will be sent as an argument
// to all event "sinks", or listeners.
SubmitEventArgs e = new SubmitEventArgs(contribution, contributor);
if (Submission != null)
{
Console.WriteLine("Broadcasting...");
// Raise Event. This calls the remote listening method on all clients of this object.
Submission(this, e);
}
}
}
// Class to be used by clients to submit delegates to the ChatCoordinator object.
// Clients must derive a custom class from this and override the InternalSubmissionCallback
// function. InternalSubmissionCallback is where they need to implement their callback
// logic. They must use the SubmissionCallback function in their remotable delegates.
public abstract class RemotelyDelegatableObject : MarshalByRefObject
{
public void SubmissionCallback (object sender, SubmitEventArgs submitArgs)
{
InternalSubmissionCallback (sender, submitArgs) ;
}
protected abstract void InternalSubmissionCallback (object sender, SubmitEventArgs submitArgs) ;
}
Note that the ChatCentral.cs file has not changed:
// ChatCentral.cs
using System;
using System.Runtime.Remoting;
public class ServerProcess
{
// This simply keeps the ChatCoordinator application domain alive.
public static void Main(string[] Args)
{
RemotingConfiguration.Configure("ChatCentral.exe.config");
Console.WriteLine("The host application domain is running. Press Enter again to stop the application domain.");
Console.ReadLine();
}
}
}
The ChatClient.cs file must be rewritten to look like the following.
Note that MyCallbackClass now contains the callback function. It is derived
from RemotelyDelegatableObject.
// ChatClient.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class ChatClient
{
private string _alias = null;
public ChatClient(string alias)
{
this._alias = alias;
}
public void Run()
{
RemotingConfiguration.Configure("ChatClient.exe.config");
// Create a proxy to the remote object.
ChatCoordinator chatcenter = new ChatCoordinator();
// Create a remotely delegatable object
MyCallbackClass callback = new MyCallbackClass (_alias) ;
// Create a new delegate for the method that you want the event to call
// when it occurs on the server. The SubmissionReceiver method will
// then be a remote call for the server object; therefore, you must
// register a channel to listen for this call back from the server.
chatcenter.Submission += new SubmissionEventHandler(callback.SubmissionCallback);
String keyState = "";
while (true)
{
Console.WriteLine("Press 0 (zero) and ENTER to Exit:\r\n");
keyState = Console.ReadLine();
if (String.Compare(keyState,"0", true) == 0)
break;
// Call the server with the string you submitted and your alias.
chatcenter.Submit(keyState, _alias);
}
chatcenter.Submission -= new SubmissionEventHandler(callback.SubmissionCallback);
}
// Args[0] is the alias that will be used.
public static void Main(string[] Args)
{
if (Args.Length != 1)
{
Console.WriteLine("You need to type an alias.");
return;
}
ChatClient client = new ChatClient(Args[0]);
client.Run();
}
}
// MyCallbackClass is the class that contains the callback function to
// which ChatClient will submit a delegate to the server.
// To to pass a reference to this method (that is, a delegate)
// across an application domain boundary, this class must extend
// MarshalByRefObject or a class that extends MarshallByRefObject like all
// other remotable types. MyCallbackClass extends RemotelyDelegatableObject because
// RemotelyDelegatableObject is a class that the server can obtain type information
// for.
class MyCallbackClass : RemotelyDelegatableObject
{
private string _alias = null;
public MyCallbackClass () {}
public MyCallbackClass (string alias) { _alias = alias ; }
// InternalSubmissionCallback is the method that is called by
// RemotelyDelegatableObject.SubmissionCallback(). SubmissionCallback() is
// sent to the server via a delegate. You want the chat server to call
// when the Submission event occurs -- even if the submission is yours.
// The SubmissionEventHandler delegate wraps this function and is passed
// to the Add_Submission (SubmissionEventHandler delegate) member of
// the ChatCoordinator object. The .NET Remoting system handles the transfer
// of all information about the client object and channel necessary to
// make a return remote call when the event occurs.
protected override void InternalSubmissionCallback (object sender, SubmitEventArgs submitArgs)
{
// Block out your own submission.
// This simple chat server does not filter anything.
if (String.Compare(submitArgs.Contributor, _alias, true) == 0)
{
Console.WriteLine("Your message was broadcast.");
}
else
Console.WriteLine(submitArgs.Contributor
+ " says:\r\n"
+ new String('-', 80)
+ submitArgs.Contribution
+ "\r\n"
+ new String('-', 80)
+ "\r\n");
}
// This override ensures that if the object is idle for an extended
// period, waiting for messages, it won't lose its lease. Without this
// override (or an alternative, such as implementation of a lease
// sponsor), an idle object that inherits from MarshalByRefObject
// may be collected even though references to it still exist.
public override object InitializeLifetimeService()
{
return null;
}
}
The ChatCentral.exe.config file has not changed. It is still defined as
follows:
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
mode="Singleton"
type="ChatCoordinator, ChatCoordinator"
objectUri="Chat"
/>
</service>
<channels>
<channel
ref="http"
port="8080"
/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
The ChatClient.exe.config file has also not changed. It is defined as
follows:
<configuration>
<system.runtime.remoting>
<application>
<client>
<wellknown
type="ChatCoordinator, ChatCoordinator"
url="http://localhost:8080/Chat"
/>
</client>
<channels>
<!-- The "0" port is declared to allow remoting to choose -->
<!-- the most appropriate channel. You must specify a channel -->
<!-- here, however; if you do not do so, your ChatClient -->
<!-- will not be listening for the call back from the -->
<!-- ChatCoordinator object. -->
<channel
ref="http"
port="0"
/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
The build instructions for this sample have also not changed. The
instructions in the .NET SDK use the command-line compilers.