понедельник, 6 января 2014 г.

WCF push

Взято отсюда:   http://habrahabr.ru/post/128634/

Создание Push Notification сервиса на основе WCF RESTиз песочницы

В качестве вступления

Модель push-нотификаций является распространённой моделью для обмена сообщениями. Она подразумевает не получение информации по запросу, а немедленную её передачу отправителю при появлении этой информации на сервере.

Стандартный подход с ипользованием wsDualHttpBinding

Возможность создания push-механизма предоставляет и WCF. Этот фреймворк позволяет создать push-сервис с использованием wsDualHttpBinding контракта. Такой контракт позволяет для каждого запроса определить метод обратного вызова, который будет вызван при наступлении какого-либо события.
Если применить этот механизм к системе обмена сообщениями, то получим следующий алгоритм:

— Для каждого запроса на новые сообщения создаётся callback, который сохраняется в списке подписчиков на новые сообщения.
— При получении нового сообщения, система проходит по списку подписчиков и находит нужного нам получателя сообщения (а значит и нужный callback).
— Вызываем нужный нам callback-метод.
Ниже приведён пример использования wsDualHttpBinding для WCF сервиса:

— Создаём метод обратного вызова для запроса на новые сообщения
interface IMessageCallback
    { 
        [OperationContract(IsOneWay = true)]
        void OnMessageAdded(int senderId, string message, DateTime timestamp);
    }

— Создаём сервис контракт
    [ServiceContract(CallbackContract = typeof(IMessageCallback))]
    public interface IMessageService
    {
        [OperationContract]
        void AddMessage(int senderId, int recipientId, string message);

        [OperationContract]
        bool Subscribe();
    }

— Создаём сам сервис
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);
               }
         });
     }
}

— Конфигурируем сервис в файле web.config
<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>

Сервис готов.

Однако, эта модель работает только в том случае, когда и сервер, и подписчики являются .NET-приложениями.

Использование RESTful подхода

Вышеописанный метод не подходит для случаев, когда подписчик, к примеру, является мобильным устройством и может использовать только запросы в формате REST.
В этом случае на помощь приходят асинхронные REST-запросы.

Итак, создадим сервис, аналогичный по функциональности предыдущему, только на основе REST.
В случае с асинхронной моделью запрос состоит из двух частей: BeginRequestName и EndRequestName.

— Определим ServiceContract для REST-сервиса
    [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);
    }

Обратите внимание: EndGetMessage не помечен аттрибутом OperationContact.

— Создадим класс для асинхронного результата, реализующий интерфейс IAsyncResult
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;
            }
        }

}

Помимо реализации интерфейса, в этом классе также хранится Id получателя сообщения (recipientId), а также само сообщение, которое будет доставлено отправителю(result).

— Теперь реализуем сам сервис
[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;
        }
    }

Когда приходит запрос на получение нового сообщения, создаётся асинхронный результат, который добавляется в список подписчиков. Как только приходит сообщение для данного подписчика, свойство IsCompleted для данного IAsyncResult устанавливается в true, и вызывается метод EndGetMessage. В EndGetMessage отправляется ответ подписчику.

— Осталось сконфигурировать сервис в файле web.config
<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>

Сервис готов.

Очевидно, что при истечении времени ожидания ответа от сервиса, нужно будет переотправлять запрос на получение новых сообщений.

Заключение

Таким образом можно реализовать Push-сервис для обмена сообщениями “в реальном времени”, основываясь на REST запросах. Такой сервис может использоваться с любого клиента, поддерживающиего RESTful реквесты, в том числе и из обычного браузера.



Комментариев нет:

Отправить комментарий