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-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.
Appendix D presents a number of approaches for supporting a better design of events, called the publish-subscribe pattern.
3.16.130.201