资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

PushDataFromServertoSilverlightClientsWithaWCFDuplexService

Normally in a client-server architecture application, the client makes a request to the server, then the server responses to the client. The client side PULLs data from the server side. However, we sometimes want the server side to PUSH data to the client side whenever the data changed. With a WCF duplex service, we can push updated data to Silverlight clients. In the rest of this blog I will show you how to achieve this goal.

成都创新互联公司是一家集网站建设,乐东黎族企业网站建设,乐东黎族品牌网站建设,网站定制,乐东黎族网站建设报价,网络营销,网络优化,乐东黎族网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。

Many of the WCF services out there follow the simple request-response mechanism to exchange data which works well for many applications. However, in addition to standard HTTP bindings, WCF also supports several others including a polling duplex binding made specifically for Silverlight which allows a service to push data down to a client as the data changes. This type of binding isn't as "pure" as the push model available with sockets since the Silverlight client does poll the server to check for any queued messages, but it provides an efficient way to push data to a client without being restricted to a specific port range. Once a communication channel is opened messages can be sent in either direction. The Silverlight SDK states the following about how communication works between a Silverlight client and a duplex service:

"The Silverlight client periodically polls the service on the network layer, and checks for any new messages that the service wants to send on the callback channel. The service queues all messages sent on the client callback channel and delivers them to the client when the client polls the service."

I still use a sample application to demonstrate it.

1. Creating Base Contracts

When creating a WCF duplex service for Silverlight, the server creates a standard interface with operations. However, because the server must communicate with the client it also defines a client callback interface. The interfaces are defined as below.

IUniversalDuplexContract

[ServiceContract(Name="DuplexService", CallbackContract = typeof(IUniversalDuplexCallbackContract))]
public interface IUniversalDuplexContract
{
[OperationContract(IsOneWay = true)]
void SendToService(DuplexMessage msg);
}

This interface is a little different from the standard WCF interfaces you may have seen or created. First, it includes a CallbackContract property that points to the client interface. Second, the SendToService() operation is defined as a one way operation. Client calls are not immediately returned as a result of setting IsOneWay to true and are pushed to the client instead.

IUniversalDuplexCallbackContract

[ServiceContract]
public interface IUniversalDuplexCallbackContract
{
//[OperationContract(IsOneWay = true)]
//void SendToClient(DuplexMessage msg);

[OperationContract(IsOneWay = true, AsyncPattern = true)]
IAsyncResult BeginSendToClient(DuplexMessage msg, AsyncCallback acb, object state);
void EndSendToClient(IAsyncResult iar);
}

The IUniversalDuplexCallbackContract interface allows a message to be sent back to the client by calling the SendToClient() method.

2. Creating Base Duplex Service

Once the server and client contracts are defined a service class can be created that implements the IUniversalDuplexContract interface.

DuplexService

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public abstract class DuplexService : IUniversalDuplexContract
{
object syncRoot = new object();
Dictionary clients = new Dictionary();

///


/// This will be called when a new client is connected
///

/// Session ID of the newly-connected client
protected virtual void OnConnected(string sessionId) { }

///
/// This will be called when a client is disconnected
///

/// Session ID of the newly-disconnected client
protected virtual void OnDisconnected(string sessionId) { }

///


/// This will be called when a message is received from a client
///

/// Session ID of the client sending the message
/// The message that was received
protected virtual void OnMessage(string sessionId, DuplexMessage message) { }

///


/// Pushes a message to all connected clients
///

/// The message to push
protected void PushToAllClients(DuplexMessage message)
{
lock (syncRoot)
{
foreach (string session in clients.Keys)
{
PushMessageToClient(session, message);
}
}
}

///


/// Pushes a message to one specific client
///

/// Session ID of the client that should receive the message
/// The message to push
protected void PushMessageToClient(string clientSessionId, DuplexMessage message)
{
IUniversalDuplexCallbackContract ch = clients[clientSessionId];

IAsyncResult iar = ch.BeginSendToClient(message, new AsyncCallback(OnPushMessageComplete), new PushMessageState(ch, clientSessionId));
if (iar.CompletedSynchronously)
{
CompletePushMessage(iar);
}
}

void OnPushMessageComplete(IAsyncResult iar)
{
if (iar.CompletedSynchronously)
{
return;
}
else
{
CompletePushMessage(iar);
}
}

void CompletePushMessage(IAsyncResult iar)
{
IUniversalDuplexCallbackContract ch = ((PushMessageState)(iar.AsyncState)).ch;
try
{
ch.EndSendToClient(iar);
}
catch (Exception ex)
{
//Any error while pushing out a message to a client
//will be treated as if that client has disconnected
System.Diagnostics.Debug.WriteLine(ex);
ClientDisconnected(((PushMessageState)(iar.AsyncState)).sessionId);
}
}


void IUniversalDuplexContract.SendToService(DuplexMessage msg)
{
//We get here when we receive a message from a client

IUniversalDuplexCallbackContract ch = OperationContext.Current.GetCallbackChannel();
string session = OperationContext.Current.Channel.SessionId;

//Any message from a client we haven't seen before causes the new client to be added to our list
//(Basically, treated as a "Connect" message)
lock (syncRoot)
{
if (!clients.ContainsKey(session))
{
clients.Add(session, ch);
OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
OnConnected(session);
}
}

//If it's a Disconnect message, treat as disconnection
if (msg is DisconnectMessage)
{
ClientDisconnected(session);
}
//Otherwise, if it's a payload-carrying message (and not just a simple "Connect"), process it
else if (!(msg is ConnectMessage))
{
OnMessage(session, msg);
}
}

void Channel_Closing(object sender, EventArgs e)
{
IContextChannel channel = (IContextChannel)sender;
ClientDisconnected(channel.SessionId);
}

void Channel_Faulted(object sender, EventArgs e)
{
IContextChannel channel = (IContextChannel)sender;
ClientDisconnected(channel.SessionId);
}

void ClientDisconnected(string sessionId)
{
lock (syncRoot)
{
if (clients.ContainsKey(sessionId))
clients.Remove(sessionId);
}
try
{
OnDisconnected(sessionId);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}

//Helper class for tracking both a channel and its session ID together
class PushMessageState
{
internal IUniversalDuplexCallbackContract ch;
internal string sessionId;
internal PushMessageState(IUniversalDuplexCallbackContract channel, string session)
{
ch = channel;
sessionId = session;
}
}
}

The DuplexService can be used as base class of other business services.

3. Creating Base Duplex Service Factory

DuplexServiceFactory

///


/// Derive from this class to create a duplex Service Factory to use in an .svc file
///

/// The Duplex Service type (typically derived from DuplexService)
public abstract class DuplexServiceFactory : ServiceHostFactoryBase
where T : IUniversalDuplexContract, new()
{
T serviceInstance = new T();

///
/// This method is called by WCF when it needs to construct the service.
/// Typically this should not be overridden further.
///

public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
ServiceHost service = new ServiceHost(serviceInstance, baseAddresses);
CustomBinding binding = new CustomBinding(
new PollingDuplexBindingElement(),
new BinaryMessageEncodingBindingElement(),
new HttpTransportBindingElement());

service.Description.Behaviors.Add(new ServiceMetadataBehavior());
service.AddServiceEndpoint(typeof(IUniversalDuplexContract), binding, "");
service.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
return service;
}
}

The factory is responsible for creating the appropriate host while the host defines the service endpoint.

4. Create Base Duplex Message

///


/// Base message class. Please add [KnownType] attributes as necessary for every
/// derived message type.
///

[DataContract(Namespace = "http://samples.microsoft.com/silverlight2/duplex")]
[KnownType(typeof(ConnectMessage))]
[KnownType(typeof(DisconnectMessage))]
[KnownType(typeof(LiveDataMessage))]
public class DuplexMessage { }

Any business objects intend to be pushed from the duplex service to silverlight clients must derive from this DuplexMessage class, and with a [KnownType] attribute.

Now we have constructed the infrastructure of the duplex service. Next Let's create a concrete duplex business service to push business data to silverlight clients.

5. Create a Business Service

LiveDataMessage

[DataContract]
public class LiveDataMessage: DuplexMessage
{
[DataMember]
public int Value { get; set; }
[DataMember]
public string Description { get; set; }
}

LiveDataService

public class LiveDataService :DuplexService
{
Timer liveDataTimer;

public LiveDataService()
{
//Set up a an update every 5 seconds
this.liveDataTimer = new Timer(new TimerCallback(LiveDataUpdate),null, 0, 5000);
}

void LiveDataUpdate(object o)
{
LiveDataMessage liveDataMessage = new LiveDataMessage()
{
Description = "Live Data at " + DateTime.Now.ToLongTimeString(),
Value = new Random().Next(0, 100)
};
PushToAllClients(liveDataMessage);
}
}

LiveDataService.svc

<%@ ServiceHost
Language="C#"
Debug="true"
Service="DuplexExample.Web.LiveDataService"
%>

6. Config the Duplex Service

The Web.config file looks like below.