Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Implementing a Publisher/Subscriber model for .NET Remoting

0.00/5 (No votes)
13 Jun 2005 1  
An article about event propagation in a distributed environment.

Introduction

.NET Remoting does not support an event model that will allow us to assign a handler to a remote event's publisher. You might think that this can be easily accomplished by passing to the remote service the reference of a client object, allowing the remote service to use this reference as a call back for any worthy event.

You can try it out and be surprised how well it works in your development environment on your desktop. But remember that in a distributed computing environment all components must perform irregardless of whether the other components are available or not. Think what would happen if the service component that holds on to a reference to call back the client would be stopped and started again. The client's reference would probably go away and without it the client component would never see an event again. You can apply this thinking again on behalf of the client component. Would the client not have to re-register the event handler?

This article describes a Publisher/Subscriber model which simply relies on the exchange of the well-known endpoints. Let the publisher know where to send the event notifications. It is nothing but a simple text string like http://ComputerName:9876/ServiceName. When the publisher has something to publish, it simply connects to the client's or event subscriber's service and fires off the notification.

The event subscriber just needs to set itself up as a server that publishes an event sink. And the event publisher needs to establish the necessary client channels. Nothing is simpler than this.

Here is the publisher's remoting configuration:

<configuration>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="Http" port="8123" />
            </channels>
            <service>
                <wellknown>
                    mode="Singleton"
                    type="RemoteEvents.RemoteEventsPublisher, Publisher"
                    objectUri="Publisher" />

Here is the subscriber's remoting configuration:

<configuration>
    <system.runtime.remoting>
        <application>
            <channels>
                <channel ref="Http" port="8456" />
            </channels>
            <service>
                <wellknown>
                    mode="Singleton"
                    type="RemoteEvents.ApplicationEventSink, Subscriber"
                    objectUri="Subscriber" />

You may download the source code, compile it with Visual Studio 2003, and run the two console applications, Publisher and Subscriber. You can start the applications in any order. If you start the subscriber application first, you should see a series of exceptions until the publisher application is started as well. Eventually, the flow of exceptions will come to a halt and a DateTime stamp is displayed every two seconds as transmitted by the publisher. You can also tear down any of the two applications and then bring it up again. You should see the resumption of the event transmissions.

The rest of this article explains the module a little bit, that how I decided to implement it.

The Common Contract

The subscriber must implement an application events sink to which the publisher will send the events. The interface definition to this application events sink must be placed into an assembly which is shared between the subscriber and the publisher. The publisher practically defines this events sink interface since it is the source of the events. For our example purpose, this common contract was defined like so:

// this interface is shared between the publisher and subscriber            

public interface IApplicationEventsSink 
{
    void OnTime(TimeEventArgs args);
    void OnClick(ClickEventArgs args);
}

The method arguments TimeEventArgs and ClickEventArgs have been defined as serializable objects and are also part of the common contract. Their definitions are found in the shared assembly as well. One other important common definition is the interface to the publisher.

// the publisher implements this interface and the subscriber uses it            

public interface IRemoteEventsPublisher
{
    void AddSubscription(Type type, string url);
    void RemoveSubscription(Type type, string url);
}

The Subscriber

The subscriber connects to the publisher via the IRemoteEventsPublisher interface.

// first connect to the remote publisher        

string url = "http://localhost:8123/Publisher";
IRemoteEventsPublisher pub = (IRemoteEventsPublisher)
  RemotingServices.Connect(typeof(IRemoteEventsPublisher), url);

// add a subscription for the IApplicationEventsSink

// and pass along the local address for the

// publisher to call back on

pub.AddSubscription(typeof(IApplicationEventsSink), 
                    "http://localhost:8456/Subscriber");

The subscriber must implement the IApplicationEventsSink as a remote object, that is a remote object for the publisher to call back upon.

// subscribers implementation of IApplicationEventsSink

class ApplicationEventSink : MarshalByRefObject, IApplicationEventsSink
{
    public void OnTime(TimeEventArgs args)
    {
        Console.WriteLine(args.Time);
    }
    public void OnClick(ClickEventArgs args)
    {
        Console.WriteLine(args.Value);
    }
}

The publisher will somehow store the information about the subscriber's callback URL and interface and pass the events by making a remote call to the subscriber. There is no more to be said about the subscriber.

The Publisher

To receive and store subscriptions, the publisher relies upon an EventsSubscriptionManager which acts behind the IRemoteEventsPublisher interface. Here is the publisher's implementation of this interface:

public class RemoteEventsPublisher : MarshalByRefObject, 
                                     IRemoteEventsPublisher
{
    public void AddSubscription(Type type, string url) {
        SubscriptionsManager.AddSubscription(type, url);
    }
 
    public void RemoveSubscription(Type type, string url) {
        SubscriptionsManager.RemoveSubscription(type, url);
    }
}

It is obviously just a thin wrapper class. Since events are published via an interface known to both the subscriber and the publisher, say IApplicationEventsSink, the publisher might also implement this common interface as a matter of convenience.

class ApplicationEventsSink : IApplicationEventsSink
{
    public ApplicationEventsSink()
    {
        // let the subsctiption manager know

        // about the even sink it needs to support

        SubscriptionsManager.SupportSubscription(
                 typeof(IApplicationEventsSink));
    }

    public void OnTime(TimeEventArgs args)
    {
        // get all the subscriptions for the "OnTime"

        // event of the IApplicationEventsSink

        EventSubscription[] Subscriptions = 
           SubscriptionsManager.GetSubscriptions(
                 typeof(IApplicationEventsSink), "OnTime");
        
        // fire this event for each subscription

        foreach(EventSubscription sub in Subscriptions)
            sub.DispatchEvent(args);
    }

    public void OnClick(ClickEventArgs args)
    {
        // get all the subscriptions for the "OnClick"

        // event of the IApplicationEventsSink

        EventSubscription[] Subscriptions = 
          SubscriptionsManager.GetSubscriptions(
                typeof(IApplicationEventsSink), "OnClick");
        
        // fire this event for each subscription

        foreach(EventSubscription sub in Subscriptions)
            sub.DispatchEvent(args);
    }
}

This class reveals a little of how the subscriptions manager is engaged in the dispatch of events. Internally, the subscription manager maintains a list of subscriptions for each event on every event sink that it supports. The event subscription is added and/or removed via the events publisher's remote interface. In our case, it is about the IApplicationEventsSink and its OnClick and OnTime events.

One important feature of the subscription manager is persistence of the event subscriptions. You should download the code and look at the subscription manager's implementation. It is not very complicated.

The source code

The solution consists of four projects, the EventsSubscriptionManager, the Publisher, the Subscriber, and Shared. The Publisher depends on the EventsSubscriptionManager, and Shared is shared between the Subscriber and Publisher for its common interface and object definitions.

On startup, the subscriber will try to add a subscription to the remote publisher. In case the publisher is not up and running, the subscriber will continue to connect to the publisher to add the subscription. The publisher, when up and running, will step inside a loop and continuously fire events. You should be able to start either the publisher or the subscriber, and eventually get a successful event flow. While one is up and the other is down, exceptions are thrown and caught and printed to the console to indicate the disconnected state of the system.

Conclusion

Remote event propagation has been a topic for others to write about as well. Here is another article on this subject. There is never a right solution. You will need to consider your application requirements before anything else. Also, what I have to offer is just an idea and a prototype to go with. There is one more, perhaps important, thing to consider. In my model, the publisher never knows who submits a subscription request. It simply delivers the events to the URL that is submitted. This means that one client can enter a subscription on behalf of another client, a feature that you may have been waiting for to either embrace or to avoid.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here