Publish/Subscribe en WCF

Este post nace a partir de un problema que me comento Rodrigo en el TTT (hay que ver como se pone cuando tiene un problema da miedo!!). No voy a comentar el problema de Rodrigo, sino que voy a explicar como se realiza un sistema Publish/Subscribe en WCF.

Lo primero es explicar que en el sistema Publish/Subscribe los cliente se subscriben en el servidor a determinadas operaciones y el servidor les envia mensajes cuando una operación determinada ocuerre en el servidor. Con este sistema estamos evitando el sistema de que el cliente este preguntando periodicamente al servidor si ha ocurrido el evento que espera, sino que es el servidor quien informa a todos los clientes que se hayan suscrito cuando se produzca el evento.

Con remoting teniamos tambien esta posibilidad a traves de MarshalByRefObject, ahora en WCF lo implementamos a traves de CallbackContract, basicamente en la definición del servicio especificamos que interfaz va a ser usuado en el callback. Un interfaz callback podemos decir que es un interfaz en las que se definen las operaciones que el servidor va a poder enviar a los clientes suscritos.

Para explicar mejor esto como siempre mejor con un ejemplo, para el ejemplo vamos a mostrar un sistema de alarma en el cual los clientes se susbcribiran con una hora a la cual quiere que se les recuerde y un mensaje, y el servidor se encargara de resordarselo cada minuto. Un ejmeplo sencillo pero que nos da idea de lo que podemos alcanzar.

Lo primero que vamos a ver es el servidor en el definimos la interfaz 

 
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace ContractsLibrary
{
    [ServiceContract(
        Name="AlarmServer",
        CallbackContract=typeof(IAlarmCallback),
        SessionMode=SessionMode.Required)]
    public interface IAlarmServer
    {
        [OperationContract(IsOneWay = true)]
        void RegisterAlarm(DateTime alarmTime, string clientName, string reminderMessage);
    }
}

Como podemos ver en los atributos del servicio hemos definido cual es el contrato del callback (interfaz) que deben de cumplir los clientes. Por una parte nosotros solo tenemos un metodo que el el registro de la hora de alarma, mensaje y cliente

En el mismo sevidor debemos de definir el interfaz IAlarmCallback que en nuestro caso es

 

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace ContractsLibrary
{
    public interface IAlarmCallback
    {
        [OperationContract(IsOneWay = true)]
        void SignalAlarm(string reminderMessage);
    }
}

Este interfaz debe de ser implementado en la clase cliente, como ya veremos. para terminar con el servidor implementamos el interfaz IAlarmServer que utilzaran los clientes para subscribirse. El codigo en este caso seria

 

 

using System;
using System.Timers;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using ContractsLibrary;

namespace AlarmHost
{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
    public class AlarmServer : IAlarmServer
    {
        private RegisteredAlarm alarm = null;
        private Timer _timer;

        public AlarmServer()
        {            
            _timer = new Timer();
            _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
            _timer.Interval = 60000; // 1 minuto
            _timer.Start();
        }

        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if ((DateTime.Now <= alarm.AlarmTime) && !alarm.Fired)
            {
                alarm.Callback.SignalAlarm(alarm.Message);
                alarm.Fired = true;
            }
        }
        #region IAlarmServer Members

        public void RegisterAlarm(DateTime alarmTime, string clientName, string reminderMessage)
        {
            alarm = new RegisteredAlarm();
            alarm.AlarmTime = alarmTime;
            alarm.ClientName = clientName;
            alarm.Message = reminderMessage;
            alarm.Callback = OperationContext.Current.GetCallbackChannel<IAlarmCallback>();
            
            System.Diagnostics.Debug.WriteLine("alarma registrada para el cliente" + clientName + ".");
        }

        #endregion
    }
}

En este caso si nos fijamos hemos puesto que el modo de instanciacion sea por sesion pero tambien podemos poner Singleton, eso dependera de como queremos utilizarlo. La parte importante de este codigo es la linea

alarm.Callback = OperationContext.Current.GetCallbackChannel<IAlarmCallback>();

En esta linea estamos guardando en la propiedad Callback una referencia al contrato de devolución de llamada llamando a OperationContext.Current.GetCallbackChannel para realizar la devolución de llamada.

La propiedad Callback de alarma tiene la definición

public IAlarmCallback Callback
        {
            get { return _callback; }
            set { _callback = value; }
        }

Es decir es del tipo del interfaz que hemos definidio como Callback.

Por ultimo nos queda en el servidor definir nuestro fichero de configuración

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <services>
      <service behaviorConfiguration="MetaEnabledBehavior" name="AlarmHost.AlarmServer">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8080/AlarmServer"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="netTcpBinding" contract="ContractsLibrary.IAlarmServer"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MetaEnabledBehavior">
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Ahora en nuestro cliente creamos el proxy con svcutil como siempre, pero nos queda implementar el interfaz de callback que en nuestro caso era IAlarmCallback, pero debemos de implementar la que se genere cuando creamos el proxy en el cliente, en nuestro caso la implementación sera

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace AlarmClient
{
    public class AlarmCallback : AlarmServerCallback
    {
        #region AlarmServerCallback Members

        public void SignalAlarm(string reminderMessage)
        {
            MessageBox.Show("El servidor te recuerda que: " + reminderMessage);
        }

        #endregion
    }
}

Una vez implementado en el cliente solo nos que el codigo que nos permite subscribirnos al servicio que en este caso sera

 

namespace AlarmClient
{
    public partial class MainForm : Form
    {
        private AlarmServerClient client = null;
        public MainForm()
        {
            InitializeComponent();
            InstanceContext context = new InstanceContext(new AlarmCallback());
            client = new AlarmServerClient(context);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            client.RegisterAlarm(dateTimePicker1.Value, clientName.Text, reminderMessage.Text);
            MessageBox.Show("Alarma registrada en el cliente.");
        }
    }
}

De esta manera podemos tener diferentes clientes que se han suscrito al sistema de alarma y el servidor les respondera a todos ellos a traves de SignalAlarm.

 

Existen otras formas de implementar Publish/Subscribe como son usando MSQM o Straming, intentare hablar de ellas mas adelante si el trabajo me lo permite.

5 comentarios sobre “Publish/Subscribe en WCF”

  1. Jajajajaj… Oskar, tienes toda la razón, cuando tengo algo entre manos, y no entiendo el por qué, no puedo parar hasta que lo entiendo y lo soluciono.

    La verdad es que a veces es facil perderse entre las mil opciones que tiene WCF y comprender como exactamente afectan al comportamiento de nuestros servicios.

    Sobre el interesante tema que tratas en este post hay un excelente artículo de Juwal Lowy, en el que expone como implmentar publicador – subscriptor en WCF, incluyendo susbcripciones persistentes: http://msdn.microsoft.com/msdnmag/issues/06/10/wcfessentials

  2. Hombre Rodrigo ya le habia echado un vistazo al articulo, pero creia que era demasiado nivel para empezar a explicar este sistema. La verdad que tu error era curioso curioso, menos mal que tuvistes lo que yo llamo en estos casos «intuición del programador».
    Sobre lo de los parametros de WCF creo en mi humilde opinión que se han pasado con ellos o que no hay suficiente información de ellos

Responder a alexphantom Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *