📜 ⬆️ ⬇️

Creating a push notification service based on WCF REST

As an introduction

The push notification model is a common messaging model. It implies not receiving information on request, but sending it immediately to the sender when this information appears on the server.

Standard wsDualHttpBinding approach

The ability to create a push mechanism provides and WCF. This framework allows you to create a push service using the wsDualHttpBinding contract. Such a contract allows for each request to define a callback method that will be called upon the occurrence of an event.
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.
- When a new message is received, the system goes through the list of subscribers and finds the message recipient we need (and therefore the desired callback).
- Call the callback method we need.
Below is an example of using wsDualHttpBinding for a WCF service:

- Create a callback method for requesting new messages
interface IMessageCallback { [OperationContract(IsOneWay = true)] void OnMessageAdded(int senderId, string message, DateTime timestamp); } 

- 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<IMessageCallback> subscribers = new List<IMessageCallback>(); public bool Subscribe(int id) { try { IMessageCallback callback = OperationContext.Current.GetCallbackChannel<IMessageCallback>(); 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); } }); } } 

- Configuring the service in the web.config file
 <system.serviceModel> <services> <service name="WCFPush.MessageService" behaviorConfiguration="Default"> <endpoint address ="" binding="wsDualHttpBinding" contract="WCFPush.IMessage"> </endpoint> </service> </services> <behaviors> <serviceBehaviors> <behavior name="Default"> <serviceMetadata httpGetEnabled="True"/> <serviceDebug includeExceptionDetailInFaults="False" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> 

Service is ready.
')
However, this model only works when both the server and subscribers are .NET applications.

Using the RESTful approach

The above method is not suitable for cases where the subscriber, for example, is a mobile device and can use only REST requests.
In this case, asynchronous REST requests come to the rescue.

So, we will create a service similar in functionality to the previous one, based only on REST.
In the case of an asynchronous model, the request consists of two parts: BeginRequestName and EndRequestName.

- Define ServiceContract for 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 recipient's Id (recipientId), as well as the message itself, which will be delivered to the sender (result).

- Now we realize the service itself
 [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)] public class MessageService : IMessageService { private static List<MessageAsyncResult> subscribers = new List<MessageAsyncResult>(); 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 comes in to receive a new message, an asynchronous result is created, which is added to the list of subscribers. As soon as a message arrives for a given 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
 <system.serviceModel> <bindings> <webHttpBinding> <binding name="webBinding"> </binding> </webHttpBinding> </bindings> <services> <service name=" WCFPush.MessageService" behaviorConfiguration="Default"> <endpoint address="" contract="WCFPush.IMessageService" behaviorConfiguration="web" bindingConfiguration="webBinding" binding="webHttpBinding"> </endpoint> </service> </services> <behaviors> <endpointBehaviors> <behavior name="web"> <webHttp /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="Default"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel> 

Service is ready.

Obviously, when the response timeout from the service expires, you will need to re-send the request for new messages.

Conclusion

In this way, you can implement a Push Service for real-time messaging based on REST requests. This service can be used from any client that supports RESTful requests, including from a regular browser.

Source: https://habr.com/ru/post/128634/


All Articles