1. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
WCF i SignalR
La idea és aconseguir que un servei WCF on podem tenir allotjades les operacions de negoci d’una aplicació, pugui
donar un feedback del que està fent en temps real a l’usuari de l’aplicació.
Per poder fer això de manera molt fàcil, utilitzaré el VS2012 Professional, el Framework 4.5, i dues llibreries RC,
Microsoft ASP.NET SignalR SystemWeb i Microsoft ASP.NET SignalR Client.
2. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
Partirem de que tenim una aplicació client “activa”, en el meu cas i com a exemple, feta amb Winforms, que es
connecta a un servei que té operacions de negoci fet amb WCF.
Ara doncs es tracta d’aprofitar la potencia de SignalR per poder fer que el servidor sigui capaç d’informar al client del
que està passat en el procés de negoci invocat.
Primer de tot, crearem el servidor de missatges.
1. Crear un projecte Web buit.
2. Afegir refèrencia a Microsoft ASP.NET SignalR SystemWeb amb Nuget.
3. Afegir Global.asax.
4. Crear un Hub, amb les operacions que invocarem, i en la seva implementació i posarem les operacions
dinàmiques que subscriuran els clients.
Mirar video1: http://youtu.be/g0JiRbpK0l8
Un cop tenim el servidor de missatges “SRServer”, preparem el servei de negoci i l’aplicació client per poder
treballar com a clients del servei de missatgeria. En aquest cas, l’ aplicació client només escoltarà, o ben dit, rebrà
missatges i el servei n’enviarà. Això no vol dir que no puguin fer les dues feines els dos.
A ambdós projectes li afegim la llibreria i Microsoft ASP.NET SignalR Client, amb Nuget package manager.
Un cop fet això implementarem les subscripcions del client winforms.
En el codi del form1 i declarem unes variables:
private string urlmessenger = "http://localhost:15560/";
private string hubproxy = "BusinessMessenger";
HubConnection hubConnection = null;
Sobreescrivim el delegat de l’event “OnShown”:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
hubConnection = new HubConnection(urlmessenger);
//Creem el proxy
var messageHub = hubConnection.CreateHubProxy(hubproxy);
var schu = TaskScheduler.FromCurrentSynchronizationContext();
// ens subscrivim als metodes dinàmics del servidor
messageHub.On<string>("sendmessage", message =>
{
// do our work on the UI thread
Task.Factory.StartNew(
() =>
{
this.textBoxConsole.ForeColor = Color.White;
this.textBoxConsole.AppendText(message + Environment.NewLine);
},
CancellationToken.None,
TaskCreationOptions.None,
schu
);
});
messageHub.On<string>("sendBCmessage", message =>
{
// do our work on the UI thread
3. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
Task.Factory.StartNew(
() =>
{
this.textBoxConsole.ForeColor = Color.Yellow;
this.textBoxConsole.AppendText(message + Environment.NewLine);
},
CancellationToken.None,
TaskCreationOptions.None,
schu
);
});
hubConnection.Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
task.ContinueWith((result) =>
{
textBoxConsole.ForeColor = Color.Red;
textBoxConsole.AppendText(
String.Format("Hi ha un error obrint la conexió: {0}{1}",
result.Exception.GetBaseException(), Environment.NewLine));
}
, schu);
}
else
{
task.ContinueWith((result) =>
{
textBoxConsole.ForeColor = Color.Green;
textBoxConsole.AppendText(
String.Format("La conexió s'ha obert correctament -ConnectionId:[{0}]-{1}",
hubConnection.ConnectionId, Environment.NewLine));
}, schu);
}
}).Wait();
EnableControls();
}
private void EnableControls()
{
this.button1.Enabled = true;
this.button2.Enabled = true;
this.textBoxConsole.Enabled = true;
}
Un cop fet això ja tenim el client subscrit a dos funcions del servidor per rebre missatges.
4. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
Si ens hi fixem tenim un identificador de connexió. Aquest és el que haurà de fer servir el servei en la instància que
serveix en cada moment al client. I per tant d’alguna manera hem de passar-lo al servei. Com que els serveis podem
utilitzar-los de moltes maneres, amb sessions, sense, podem passar sempre per paràmetre l ‘id de connexió, ..., bé,
pensem. Si el passem com a paràmetre, hauríem de modificar totes les operacions de la interfície que voléssim que
enviéssim missatges, i si el client ja el tenim implementat, modificar totes les crides a aquestes operacions.
Imaginem-nos que en tenim moltes, quin rotllo. I imaginem que moltes aplicacions accedeixen a operacions del
servei de negoci, BUA!!!!!. I el codi que picarem no el podrem reutilitzar......pensem.
Bé, si no volem fer res de tot això, i volem que sempre passi el id cap al servei, independentment de com està
instanciat, si permet sessions o no, i de com tracta la concurrència, la millor manera es mitjançant un paràmetre de
capçalera. Si nosaltres som capaços de modificar la connexió client de tal manera que a cada crida d’operació li
afegim una capçalera amb el id de connexió, i a les operacions del servei que vulguin enviar un missatge al client,
puguin obtenir el id de connexió d’aquest, ja ho tenim solucionat.
Per fer-ho ens creem una classe que implementarà dues interfícies del namespace System.ServiceModel.Dispacher i
System.ServideModel.Description. I en el mètode BeforeSendRequest modificarem la capçalera del missatge.
I després, quan creem la connexió li afegirem aquesta classe com a nou comportament del client.
public class WcfClientInspector : IClientMessageInspector, IEndpointBehavior
{
static string _namespace = "http://soacat.blogspot.com";
private string _messengerConnectionId = string.Empty;
public WcfClientInspector(string messengerConnectionId)
{
_messengerConnectionId = messengerConnectionId;
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
MessageHeader<string> header =
new MessageHeader<string>(_messengerConnectionId);
request.Headers.Add(header.GetUntypedHeader("MessengerConnectionId", _namespace));
return null;
}
public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
}
5. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
public void Validate(ServiceEndpoint endpoint)
{
}
}
Ara modifiquem els delegats dels events del botons per tal de que invoquin les operacions del servei, afegint el
comportament nou.
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
RefOrderService.IOrders cliordres = CreateAndOpenClient();
await cliordres.ProcessOrdersAsync().ContinueWith((r)=>{
CloseClient((ICommunicationObject)cliordres);
button1.Enabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private async void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
RefOrderService.IOrders cliordres = CreateAndOpenClient();
await cliordres.MailingAsync().ContinueWith((s) =>
{
textBoxConsole.ForeColor = Color.Blue;
textBoxConsole.AppendText(string.Format("{0}-{1}", s.Result, Environment.NewLine));
this.textBoxConsole.SelectionStart = this.textBoxConsole.Text.Length;
this.textBoxConsole.ScrollToCaret();
CloseClient((ICommunicationObject)cliordres);
button2.Enabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private RefOrderService.IOrders CreateAndOpenClient()
{
RefOrderService.OrdersClient client = new RefOrderService.OrdersClient();
client.Endpoint.EndpointBehaviors.Add(new
WcfClientInspector(hubConnection.ConnectionId));
client.Open();
return client;
}
private void CloseClient(ICommunicationObject comobj){
comobj.Close();
}
Ara ja només ens falta el servei de negoci. Primer haurà de poder recuperar la el id de connexió del client cap el
servei de missatges, i després saber com enviar-li un missatge. Farem una funció senzilla al servei, que llegira la
capçalera requerida.
6. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
private string GetClientMessengerConnectionId()
{
int pos=OperationContext.Current.
IncomingMessageHeaders.FindHeader("MessengerConnectionId",
"http://soacat.blogspot.com");
if (pos >= 0)
{
return OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(pos);
}
else
{
return string.Empty;
}
}
Ara ja només ens falta, poder enviar missatges al client des del negoci. Els requisits mínims són la url del servei de
missatgeria i el nom del Hub que contés les operacions de missatgeria. Un cop fet això, en el constructor del servei,
inicialitzen la connexió i el Hub.
public class Orders: IOrders
{
private string _urlserver = "http://localhost:15560/";
private string _hubname = "BusinessMessenger";
private IHubConnection _hubconnection = null;
private IHubProxy _hubproxy = null;
public Orders()
{
_hubconnection = new HubConnection(_urlserver);
_hubproxy = ((HubConnection)_hubconnection).CreateHubProxy(_hubname);
// Engeguem la connexió i el HUB
StartConnection();
}
private void StartConnection()
{
((HubConnection)_hubconnection).Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
throw new FaultException<Exception>(task.Exception.GetBaseException());
}
}).Wait();
}
...
7. SOACat: http://soacat.blogspot.com ; https://twitter.com/SOACAT ;
Ara creem dues funcions per enviar missatges cap el client utilitzant el hub.
private void SendMessageToClient(string clientConnectionId, string message)
{
if(_hubconnection.State== Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
if(!string.IsNullOrEmpty(clientConnectionId))
_hubproxy.Invoke("SendTo", clientConnectionId, message).Wait();
}
private void SendMessageToAllClient(string message)
{
if (_hubconnection.State == Microsoft.AspNet.SignalR.Client.ConnectionState.Connected)
_hubproxy.Invoke("BroadCast", message);
}
I ja està, la resta al Codi Font : https://dl.dropbox.com/u/108938215/WCF_SignalR/SolutionWCF_SignalR.rar
Veure Video 2 : http://youtu.be/_X5klFffnsU