Blog Stats
  • Posts - 1
  • Articles - 0
  • Comments - 470
  • Trackbacks - 0

 

venerdì 19 agosto 2011

Condividere ServiceContract tra server .NET e client Silverlight

Recentemente ho pubblicato su codeplex SyncWcf, una semplice libreria che consente di risparmiarsi un sacco di seccature quando si vuole consumare un nostro webservice da client Silverlight.

In particolare questa libreria da il suo meglio nel caso in cui siamo noi a possedere sia il codice lato server (full .NET framework) sia il codice lato client (Silverlight).

In questo caso siamo spesso costretti ad accrocchi strani e poco funzionali: essendo Silverlight pensato per funzionare sempre in modo asincrono (giustissimo), si finisce per avere due versioni dell’interfaccia del servizio: una in versione sincrona e una asincrona, che concettualmente rapresentano però la stessa cosa

Purtroppo si è costretti a reimplementare per far felice wcf di Silverlight.

L’alternativa, che mi piace ancora meno, è far generare a visual studio tutto il codice con “add service reference”: questa secondo me è un opzione da considerare solo se il codice server side non è nostro.

Se stiamo scrivendo noi sia client che server, non ha nessun valore dover riscrivere due volte la stessa interfaccia solo per far felice wcf di Silverlight: ed ecco dove SyncWcf ci viene in aiuto.

 

SyncWcf si compone principalmente di due classi: una AsyncChannelFactory che fa il verso alla ChannelFactory di wcf (e che usa internamente) ed un AsyncChannel (che è suo fratello dell’ *Channel di wcf, come si può ben immaginare, e che usa internamente).

 

Ecco come funziona:

SyncWcf si occupa per prima cosa di trasformare l’interfaccia “sincrona” usata sul server in una che implementi l’AsyncPattern con Begin-  ed End- per ogni operazione, essendo un pattern ci riesce benissimo da solo senza bisogno di intelligenza ed errori umani.

Fatto questo la da in pasto alla solita ChannelFactory che tutta contenta ritorna un *Channel.

Tuttavia sarebbe duro usare questo channel dal nostro codice visto che si basa su un tipo generato a runtime.

Per questo il channel generato viene wrappato da un AsyncChannel il quale aggiunge l’ultimo pezzo al puzzle: si occupa di coordinare la chiamata ai metodi Begin ed End (e fare il dispatching sul thread corretto) nel modo corretto,  semplicemente usando la versione sincrona dell’interfaccia del servizio come traccia, che a questo punto può essere messa in un portable assembly e condivisa tra server e client.

Ecco un esempio di come apparirebbe il vostro codice per chiamare un metodo di un servizio nel caso decidiate di usare SyncWcf:

 

var channel = new AsyncChannelFactory<ITestService>().CreateChannel();

channel.ExecuteAsync(
    ws => ws.Operation(1),
    result =>
    {
         // [...] Do something with the result
    });

Qui “ws” è il nome che ho dato alla variabile che rappresenta il mio webservice, e che è del tipo dell’interfaccia ITestService (quella del server! non una copia o codice generato…), I parametri sono passati nel modo usuale come se invocassi una qualsiasi funzione (niente array di parametri o simili)

Pulito, chiaro, semplice da manutenere, ed assolutamente sincronizzato tra server e client, visto che l’interfaccia può essere la stessa e quindi condivisa in un portable assembly: non correrete mai il rischio di fare refactoring sul server e dimenticarvi di aggiornare la rispettiva altra metà sul client.

Un altra cosetta importante: funzionano anche chiamate ad operazioni con parametri out e ref

A me piace parecchio! lavoro con persone che non hanno mai visto Silverlight prima, e sinceramente mi sembra ridicolo proporgli di scrivere interfacce asincrone e poi codice oscuro e illegibile coi dispatcher per fare una semplicissima invocazione ad un webservice: giusto per divertimento, ecco cosa avrei dovuto scrivere invece di queste semplici 8 (sono 7 in realtà) righe se non avessi SyncWcf

 

[ServiceContract]
public interface ITestService
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginOperation(int value, AsyncCallback callback, object state);

    int EndOperation(IAsyncResult result);
}

ITestService testService = new ChannelFactory<ITestService>("*").CreateChannel();
// [...]
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
    int value = ((int)result.AsyncState).EndOperation(result);
    this.Dispatcher.BeginInvoke(delegate
    {
        // [...] Do something with the result
    });
};
testService.BeginOperation(1, asyncCallBack, testService);

hmmm… considerando che qua c’è solo un Operation… e che è un caso assolutamente semplice ed irreale, andare per la strada nativa in silverlight mi pare un opzione assolutamente impaticabile

 

In sostanza con SyncWcf ottengo due risultati

  1. consentire anche ai neofiti di chiamare webservice da Silverlight senza nessunissimo sforzo, e praticamente senza niente da imparare, senza rischi che si incasinino coi dispatcher e blocchino l’applicazione.
  2. condivido la stessa interfaccia tra server e client, risparmiando tempo ed evitando preblemi del tipo “ho dimenticato di aggiornare la service reference” (che è noioso e mi capita sempre)

 

Inoltre SyncWcf si compone di 4 classi delle quali: due sono helper interni con cui l’utilizzatore non dovrebbe aver mai niente a che fare, e altri due sono wrapper che si comportano concettualmente come ChannelFactory e *Channel di Wcf, ma semplificando enormemente le cose.

È perciò estremamente leggera , e semplice da imparare e non ha dipendenze da nessuna libreria di terze parti (chi ha detto Castle? No, neanche quella).

 

Ultimo: totale supporto all’intisense, io adoro premere “.” e vedere cosa posso fare senza dovermi ricordare niente, voi no?

 

Tanto per chiaccherare, visto che a questo punto  condividiamo tra server e client l’interfaccia del servizio ed anche I dati, possiamo condividere anche I validatori… e quindi avere il medesimo codice di validazione lato server e client, sempre nel nostro bel portable assembly…o mettere attributi sull’interfaccia del servizio e trovarceli identici sul client visto che è la medesima interfaccia… chesso se uno volesse controllare due claims ogni tanto….

 

Che ne pensate?

 

 

Copyright © Blog Author