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.

Problemilla con DSL Tools

Desde hace algún tiempo estoy en un proyecto de DSL Tools (ademas de otros 4), pñor eso me esta siendo imposible escribir. Este post es para autorecordarme y comentar un problemilla que pasa cuando se desarrolla con DSL Tools. El problema es que al Debuggear y pulsar con el botón derecho del ratón en el modelo para que se muestre el menu aparece el mensaje

{“Error HRESULT E_FAIL has been returned from a call to a COM component.”

Despues de volver un poco loco, he descubierto que DSL cachea los menus y a veces se corrompe esta cache. Para limpiar esta cache debemos ejecutar el comando

devenv /setup /rootsuffix Exp

Y a funcionar…

 

Espero que dentro de poco os pueda escribir algún post sobre DSL Tools, la cual me parece interesentisimo

 

Saludos Oskar

Añadir en Vista al Menu de Inicio la Opción Herramientas Administrativas

Ayer de churro, para que lo vamos a negar (estaba enseñando el ordenador a mi hijo de 4 meses), pulse el botón derecho del ratón en el Botón me aparecio el menu

Pulsando en propiedades por curiosidad me aparece la siguiente ventana

Pulsando en Personalizar lo siguiente que aparece una serie de opciones para personalizar el menu de inicio y entre ellas la opción de mmostrar en el menu de inicio las herramientas administrativas

 

De manera que ahora ya me aparece las herramientas administrativas en el menu de incio algo que echaba de menos