Creating a Push Notification Service Based on WCF REST
As an introduction
The push notifications model is a common messaging model. It does not mean receiving information upon request, but immediately transmitting it to the sender when this information appears on the server.
Standard approach using wsDualHttpBinding
The ability to create a push mechanism is also provided by WCF. This framework allows you to create a push service using the wsDualHttpBinding contract. Such a contract allows for each request to determine the callback method that will be called when an event occurs.
If we apply this mechanism to the messaging system, we get the following algorithm:
- For each request for new messages, a callback is created, which is stored in the list of subscribers to new messages.
- Upon receipt of a new message, the system goes through the list of subscribers and finds the recipient of the message we need (and therefore the desired callback).
- We call the callback method we need.
The following is an example of using wsDualHttpBinding for a WCF service:
- Create a callback method to request new messages
interface IMessageCallback
{
[OperationContract(IsOneWay = true)]
void OnMessageAdded(int senderId, string message, DateTime timestamp);
}
- We create a service contract
[ServiceContract(CallbackContract = typeof(IMessageCallback))]
public interface IMessageService
{
[OperationContract]
void AddMessage(int senderId, int recipientId, string message);
[OperationContract]
bool Subscribe();
}
- We create the service itself
Public class MessageService : IMessageService
{
private static List subscribers = new List();
public bool Subscribe(int id)
{
try
{
IMessageCallback callback =
OperationContext.Current.GetCallbackChannel();
callback.id = id;
if (!subscribers.Contains(callback))
subscribers.Add(callback);
return true;
}
catch
{
return false;
}
}
public void AddMessage(int senderId, int recipientId, string message)
{
subscribers.ForEach(delegate(IMessageCallback callback)
{
if ((((ICommunicationObject)callback).State == CommunicationState.Opened) && (callback.id == recipientId))
{
callback.OnMessageAdded(recipientId, message, DateTime.Now);
}
else
{
subscribers.Remove(callback);
}
});
}
}
- Configure the service in the web.config file
The service is ready.
However, this model only works if both the server and the subscribers are .NET applications.
Using a RESTful Approach
The above method is not suitable for cases when the subscriber, for example, is a mobile device and can only use requests in the REST format.
In this case, asynchronous REST requests come to the rescue.
So, we will create a service similar in functionality to the previous one, only based on REST.
In the case of the asynchronous model, the request consists of two parts: BeginRequestName and EndRequestName.
- Define a ServiceContract for a REST service
[ServiceContract]
public interface IMessageService
{
[WebGet(UriTemplate = "AddMessage?senderId={senderId}&recipientId={recipientId}&message={message}")]
[OperationContract ]
bool AddMessage(int senderId, int recipientId, string message);
[WebGet(UriTemplate = "Subscribe?id={id}")]
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginGetMessage(int id, AsyncCallback callback, object asyncState);
ServiceMessage EndGetMessage(IAsyncResult result);
}
Note: EndGetMessage is not marked with the OperationContact attribute.
- Create a class for an asynchronous result that implements the IAsyncResult interface
public class MessageAsyncResult : IAsyncResult
{
public AsyncCallback Callback { get; set; }
private readonly object accessLock = new object();
private bool isCompleted = false;
private ServiceMessage result;
private int recipientId;
private object asyncState;
public MessageAsyncResult(object state)
{
asyncState = state;
}
public int RecipientId
{
get
{
lock (accessLock)
{
return recipientId;
}
}
set
{
lock (accessLock)
{
recipientId = value;
}
}
}
public ServiceMessage Result
{
get
{
lock (accessLock)
{
return result;
}
}
set
{
lock (accessLock)
{
result = value;
}
}
}
public bool IsCompleted
{
get
{
lock (accessLock)
{
return isCompleted;
}
}
set
{
lock (accessLock)
{
isCompleted = value;
}
}
}
public bool CompletedSynchronously
{
get
{
return false;
}
}
public object AsyncState
{
get
{
return asyncState;
}
}
public WaitHandle AsyncWaitHandle
{
get
{
return null;
}
}
}
In addition to implementing the interface, this class also stores the Id of the message recipient (recipientId), as well as the message itself, which will be delivered to the sender (result).
- Now we implement the service itself
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MessageService : IMessageService
{
private static List subscribers = new List();
public bool AddMessage(int senderId, int recipientId, string message)
{
subscribers.ForEach(delegate(MessageAsyncResult result)
{
if (result.RecipientId == recipientId)
{
result.Result = new ServiceMessage(senderId, recipientId, message, DateTime.Now);
result.IsCompleted = true;
result.Callback(result);
subscribers.Remove(result);
}
});
return true;
}
public IAsyncResult BeginGetMessage(int id, AsyncCallback callback, object asyncState)
{
MessageAsyncResult asyncResult = new MessageAsyncResult(asyncState);
asyncResult.Callback = callback;
asyncResult.RecipientId = id;
subscribers.Add(asyncResult);
return asyncResult;
}
public ServiceMessage EndGetMessage(IAsyncResult result)
{
return (result as MessageAsyncResult).Result;
}
}
When a request for a new message arrives, an asynchronous result is created, which is added to the list of subscribers. As soon as a message arrives for this subscriber, the IsCompleted property for this IAsyncResult is set to true, and the EndGetMessage method is called. EndGetMessage sends a response to the subscriber.
- It remains to configure the service in the web.config file
The service is ready.
Obviously, if the response time from the service expires, you will need to resend the request for new messages.
Conclusion
Thus, it is possible to implement a push service for real-time messaging based on REST requests. Such a service can be used from any client that supports RESTful requests, including from a regular browser.