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.
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-16 demonstrates this technique, as well as the other design considerations discussed so far.
Example 5-16. 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 OnEvent2(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.
Appendix C presents a framework for supporting a better design approach for events, called the publish-subscribe pattern.
3.148.107.254