Events

The basic WCF callback mechanism does not indicate anything about the nature of the interaction between the client and the service. They may be equal peers in a commutative interaction, each calling and receiving calls from the other. However, the canonical use for duplex callbacks is with events. Events allow the client or clients to be notified about something that has occurred on the service side. An event may result from a direct client call, or it may be the result of something the service monitors. The service firing the event is called the publisher, and the client receiving the event is called the subscriber. Events are a required feature in almost any type of application, as shown in Figure 5-2.

A publishing service can fire events at multiple subscribing clients

Figure 5-2. A publishing service can fire events at multiple subscribing clients

While events in WCF are nothing more than callback operations, by their very nature events usually imply a looser relationship between the publisher and the subscriber than the typical relationship between a client and a service. When dealing with events, the service typically publishes the same event to multiple subscribing clients. The publisher often does not care about the order of invocation of the subscribers, or any errors the subscribers might have while processing the events. All the publisher knows is that it should deliver the event to the subscribers. If they have a problem with the event, there is nothing the service can do about it anyway. In addition, the service does not care about returned results from the subscribers. Consequently, event-handling operations should have a void return type, should not have any outgoing parameters, and should be marked as one-way. I also recommend factoring the events to a separate callback contract, and not mixing events with regular callbacks in the same contract:

interface IMyEvents
{
   [OperationContract(IsOneWay = true)]
   void OnEvent1();

   [OperationContract(IsOneWay = true)]
   void OnEvent2(int number);

   [OperationContract(IsOneWay = true)]
   void OnEvent3(int number,string text);
}

On the subscriber side, even when using one-way callback operations, the implementation of the event-handling methods should be of short duration. There are two reasons for this. First, if there is a large volume of events to publish, the publisher may get blocked if a subscriber has maxed out its ability to queue up callbacks because it is still processing the previous events. Blocking the publisher may prevent the event from reaching other subscribers in a timely manner. Second, if there are a large number of subscribers to the event, the accumulated processing time of each subscriber could exceed the publisher’s timeout.

The publisher may add dedicated operations to its contract, allowing clients to explicitly subscribe to or unsubscribe from the events. If the publisher supports multiple event types, it may want to allow the subscribers to choose exactly which event(s) they want to subscribe to or unsubscribe from.

How the service internally goes about managing the list of subscribers and their preferences is a completely service-side implementation detail that should not affect the clients. The publisher can even use .NET delegates to manage the list of subscribers and the publishing act itself. Example 5-14 demonstrates this technique, as well as the other design considerations discussed so far.

Example 5-14. Events management using delegates

enum EventType
{
   Event1 = 1,
   Event2 = 2,
   Event3 = 4,
   AllEvents = Event1|Event2|Event3
}
[ServiceContract(CallbackContract = typeof(IMyEvents))]
interface IMyContract
{
   [OperationContract]
   void DoSomething();

   [OperationContract]
   void Subscribe(EventType mask);

   [OperationContract]
   void Unsubscribe(EventType mask);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyPublisher : IMyContract
{
   static Action m_Event1              = delegate{};
   static Action <int> m_Event2        = delegate{};
   static Action <int,string> m_Event3 = delegate{};

   public void Subscribe(EventType mask)
   {
      IMyEvents subscriber =  OperationContext.Current.
                                               GetCallbackChannel<IMyEvents>();

      if((mask & EventType.Event1) == EventType.Event1)
      {
         m_Event1 += subscriber.OnEvent1;
      }
      if((mask & EventType.Event2) == EventType.Event2)
      {
         m_Event2 += subscriber.OnEvent2;
      }
      if((mask & EventType.Event3) == EventType.Event3)
      {
         m_Event3 += subscriber.OnEvent3;
      }
   }
   public void Unsubscribe(EventType mask)
   {
      //Similar to Subscribe() but uses -=
   }
   public static void FireEvent(EventType eventType)
   {
      switch(eventType)
      {
         case EventType.Event1:
         {
            m_Event1();
            return;
         }
         case EventType.Event2:
         {
            m_Event2(42);
            return;
         }
         case EventType.Event3:
         {
            m_Event3(42,"Hello");
            return;
         }
         default:
         {
            throw new InvalidOperationException("Unknown event type");
         }
      }
   }
   public void DoSomething()
   {...}
}

The service contract IMyContract defines the Subscribe() and Unsubscribe() methods. These methods each take an enum of the type EventType, whose individual fields are set to integer powers of 2. This enables the subscribing client to combine the values into a mask indicating the types of events it wants to subscribe to or unsubscribe from. For example, to subscribe to Event1 and Event3 but not Event2, the subscriber would call Subscribe() like this:

class MySubscriber : IMyEvents
{
   void OnEvent1()
   {...}
   void OnEvent2(int number)
   {...}
   void OnEvent3(int number,string text)
   {...}
}
IMyEvents subscriber = new MySubscriber();
InstanceContext context = new InstanceContext(subscriber);
MyContractClient proxy = new MyContractClient(context);
proxy.Subscribe(EventType.Event1|EventType.Event3);

Internally, MyPublisher maintains three static delegates, each corresponding to an event type.

Both the Subscribe() and Unsubscribe() methods check the supplied EventType value and either add the subscriber’s callback to or remove it from the corresponding delegate. To fire an event, MyPublisher offers the static FireEvent() method. FireEvent() accepts the event to fire and invokes the corresponding delegate.

Again, the fact that the MyPublisher service uses delegates is purely an implementation detail simplifying event lookup. The service could have used a linked list, although that would require more complex code.

Note

Appendix D presents a number of approaches for supporting a better design of events, called the publish-subscribe pattern.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.130.201