Introduction
Searching for an easy solution for server push technology using WCF was a bit of a hassle.
I had few requirements:
- Must use WCF
- Both the server and the client applications don't have to run while messages were being transmitted
- A common Queue for all the clients on the server side
- Server push is required - can't use polling
IIS WebService was an option but was complicated to implement with a common client Queue.
A solution was found using Microsoft Message Queuing system. Both the client and the server have a message box that use to communicate. Both applications don't have to run while messaging the opposite side.
Note
This project is not done to explain bindings, contracts and services. This is done to provide a simple server push design overview.
Requirements
- MSMQ installed (server and clients) - Go to Control Panel -> Add Remove Program -> Add Windows Component and select “Message Queuing”
- Run Visual Studio as administrator.
- Client and Server must be able to see each other (ping each other using either IP or host name). If you need external communication support - see the "Modification for public communication" section.
Overview
Modification for Public Communication
If a WebService
is required - for external internet communication, a wrapper can be made which provides a WebService
endpoint and creates an internal Queue message. This will remove the requirement for both client and server to see each other's messaging queue.
Using the Code
The flow is as follows:
Client registers itself by giving the server its own MSMQ address.
RegisterClientServiceClient registerClientServiceClient =
new RegisterClientServiceClient("RegisterClientEndPoint");
using(TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
ClientInfo clientInfo = new ClientInfo();
clientInfo.GroupName = ConfigurationManager.AppSettings["GroupName"];
clientInfo.ClientName = System.Net.Dns.GetHostName();
clientInfo.PushMessageBoxAddress = "net.msmq://" +
clientInfo.ClientName + "/private/WcfServerPush/ClientMessageBox";
registerClientServiceClient.RegisterClient(clientInfo);
scope.Complete();
}
RegisterClientServiceClient
class derives from the RegisterClientIService
and using the ClientBase
template. This is regular WCF implementation.
public class RegisterClientServiceClient :
ClientBase<RegisterClientIService>, RegisterClientIService
{
public RegisterClientServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public void RegisterClient(ClientInfo clientInfo)
{
base.Channel.RegisterClient(clientInfo);
}
}
On the server side, RegisterClient
is implemented as a regular operation which derives from the same interface service RegisterClientIService
:
public class RegisterClientService : RegisterClientIService
{
[OperationBehavior(TransactionScopeRequired =
true, TransactionAutoComplete = true)]
public void RegisterClient(ClientInfo clientInfo)
{
WcfServerPushServerBO.Instance.RegisterClient(clientInfo);
}
}
Creating the MSMQ ServiceHost
on the server side is easy:
if (!MessageQueue.Exists(registerClientMSMQName))
{
MessageQueue versionManagerQueue =
MessageQueue.Create(registerClientMSMQName, true);
versionManagerQueue.SetPermissions
(@"Everyone", MessageQueueAccessRights.FullControl);
versionManagerQueue.SetPermissions(@"ANONYMOUS LOGON",
MessageQueueAccessRights.ReceiveMessage |
MessageQueueAccessRights.PeekMessage |
MessageQueueAccessRights.WriteMessage);
}
The client side is almost the same as the server side, but with a small modification. Because we want the ability for the WPF to register an event with the ServiceHost
- whenever we get a new message, we need to create the actual service and give its instance to the ServiceHost
. Whenever we provide an instance of a service to the ServiceHost
, we must add an InstanceConextMode.Single
to the behavior:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PushMessageService : MessageBoxIService, INotifyPropertyChanged
Lastly, we need to modify the App.Config to define the Service. We can also give an http binding (not only the netMsmqBinding
) to the endpoint - if in the future we would like to provide a metadata exchange ability.
<service name="WcfServerPushServer.RegisterClientService"
behaviorConfiguration="WcfServerPushServer.RegisterClientServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001/WcfServerPush/
RegisterClientServiceHttp/" />
</baseAddresses>
</host>
<endpoint address="net.msmq://localhost/private/WcfServerPush/RegisterClientService"
binding="netMsmqBinding"
bindingConfiguration="srmpBinding"
contract="WcfServerPushIServices.RegisterClientIService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
The client side needs to recognize the endpoint as follows:
<client>
<endpoint name="RegisterClientEndPoint"
address="net.msmq://localhost/private/WcfServerPush/RegisterClientService"
binding="netMsmqBinding" bindingConfiguration="netMsmqBindingConfig"
contract="WcfServerPushIServices.RegisterClientIService"/>
</client>
Solution Architecture
WcfServerPush
- A WPF project that serves as both the client and server application. You can install the application on either side and decide yourself what to run. The WPF project is also responsible to create the message queue if not found and start the service hosts. In a real development project - it is important to separate the view from the boot bootstrapper. WcfServerPushServer
- The server side service and business objects, including the client's Queue WcfServerPushClient
- The client side serviceWcfServerPushIServices
- The message box service interface and the register client service interface. Common project for both the server and the client.
History
- February 6th 2011: First release