Utilizzare servizi WCF con callback al chiamante da app Windows 8
Introduzione
Utilizzare un servizio WCF mi è diventato così istintivo in tutte le cose che faccio che francamente non riesco più a farne a meno. Non sto qui ad elencare i vantaggi e a discutere le varie tecniche, perchè prima di tutto non ne ho le competenze (rischierei di usare termini non corretti), per cui vado diretto al succo di questo post.
Generalmente, dal punto di vista client (quindi un’applicazione WPF, un’app Windows Forms o Windows Phone, un’applicazione ASP.NET MVC, etc. etc.) un servizio WCF è un servizio al quale forniamo dati, compie elaborazioni e restituisce qualcosa. Questo avviene con chiamate a metodi esposte dal servizio tramite un contratto (un’interfaccia IQualcosa), grazie al quale noi sappiamo come comunicare, cosa passare e cosa ci aspettiamo come valori di ritorno. Questo tipo di approccio non presenta nessun tipo di problema (in realtà ce ne sono una valanga, sicurezza in primis, ma sorvoliamo): una volta che il servizio è stato esposto via http, è sufficiente aggiungerlo al nostro progetto corrente tramite il comando “Add Service Reference”. Sul nostro client viene creata una classe proxy da utilizzare per effettuare le chiamate.
Quello che succede in uno scenario di questo tipo è che il client, indipendentemente dal tipo, invoca il servizio WCF. Quest’ultimo lavora un po’ e poi restituisce i dati al client. Semplice, almeno concettualmente.
Quello su cui ho lavorato per una giornata intera è invece lo scenario seguente:
- applicazione Windows 8 Metro-style, scritta in C#
- servizio WCF in ascolto su http://localhost:1976
- il servizio WCF non solo espone dei metodi che vengono invocati dall’applicazione, ma è in grado – tramite callback ed un canale di comunicazione duplex, di invocare un metodo definito sul client
Il servizio WCF, quindi, ha diverse particolarità:
- si deve memorizzare da qualche parte il “chiamante”
- deve definire non solo un contratto col quale dialogare con il client, ma anche un contratto di callback. Quest’ultimo definisce il/i metodi da invocare sul client
Arriviamo al sodo, vediamo un po’ di codice.
Codice Server Side
Implementiamo un servizio che prende in input un ExecuteRequest e restituisce, prima o poi, un ExecuteResponse.
[DataContract] public class ExecuteRequest { [DataMember] public int Value { get; set; } } [DataContract] public class ExecuteResponse { [DataMember] public int Value { get; set; } }
Ho scritto “prima o poi” perchè supponiamo che l’esecuzione server-side sia piuttosto lunga; quindi, NON facciamo un metodo simile a questo:
[ServiceContract] public interface IExecuteService { [OperationContract] ExecuteResponse Execute(ExecuteRequest value); }
bensì facciamo come segue:
[ServiceContract(CallbackContract = typeof(IExecuteServiceCallback))] public interface IExecuteService { [OperationContract] void Execute(ExecuteRequest value); }
Invochiamo il metodo Execute passandogli un ExecuteRequest, che restituisce void. Il servizio WCF lavora, lavora, lavora, e prima o poi chiamerà la callback per informarci del risultato. La funzione di callback è indicato all’interno dell’attributo ServiceContract, ed è IExecuteServiceCallback.
public interface IExecuteServiceCallback { [OperationContract(IsOneWay = true)] void ExecuteTerminated(ExecuteResponse response); }
L’implementazione del servizio è piuttosto semplice:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public class ExecuteService : IExecuteService { private ExecuteRequest Request; private ExecuteResponse Response = new ExecuteResponse(); private IExecuteServiceCallback Callback; public void Execute(ExecuteRequest value) { this.Callback = OperationContext.Current.GetCallbackChannel<IExecuteServiceCallback>(); this.Request = value; Timer timer = new Timer(10000); timer.Elapsed += (s, e) => { timer.Stop(); this.Response.Value = this.Request.Value * 3; this.Callback.ExecuteTerminated(this.Response); }; timer.Start(); } }
Quando il cliente invoca il metodo Execute esposto dal servizio, accade che:
- il servizio si memorizza chi è il chiamante e predispone la callback
- al client restituisce un void
A solo scopo di test, parte un timer che fa aspettare il client 10 secondi, al termine dei quali viene invocata la callback sul client con il risultato dell’operazione. Praticamente, ciò che accade è che il client invoca il metodo passando un numero intero, ed il servizio restituisce il doppio di tale numero. Ripeto: è solo a scopo di test, si presuppone che un processore moderno si metta un po’ meno a calcolare il doppio di un numero!
Un pezzetto di web.config
C’è qualche accorgimento da apportare anche nel web.config. L’endpoint deve essere di tipo netHttpBinding. Come recita MSDN all’indirizzo http://msdn.microsoft.com/en-us/library/hh674273.aspx, infatti:
NetHttpBinding is a binding designed for consuming HTTP or WebSocket services and uses binary encoding by default. NetHttpBinding will detect whether it is used with a request-reply contract or duplex contract and change its behavior to match – it will use HTTP for request-reply contracts and WebSockets for duplex contracts.
Riporto qui sotto la parte più significativa del web.config:
<system.serviceModel> <services> <service name="WcfCallbackSample.ExecuteService" behaviorConfiguration="bev1"> <host> <baseAddresses> <add baseAddress="http://localhost:1000/Service" /> </baseAddresses> </host> <endpoint name="duplexendpoint" address="" binding="netHttpBinding" contract="WcfCallbackSample.IExecuteService" /> <endpoint name="MetaDataTcpEndpoint" address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="bev1"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="True" /> </behavior> </serviceBehaviors> </behaviors> <protocolMapping> <add binding="basicHttpsBinding" scheme="https" /> </protocolMapping> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> </system.serviceModel>
Ovviamente alcuni valori, attributi e nodi XML dipendono dal vostro servizio.
Client Side con un’applicazione Metro per Windows 8
Ok, abbiamo quasi finito. Adesso arriva il bello, ovvero usare il nostro servizio dal client. Dopo averlo aggiunto nei service reference, ci verrebbe istintivo utilizzare la classe client proxy che ci è stata auto-generata. No. Falso. Nada. Fermi tutti.
Innanzitutto con Visual Studio 2012 creiamo una nuova Blank app, sotto la categoria Windows Store.
Dentro troveremo un’unica Page, nel cui costruttore metteremo:
EndpointAddress address = new EndpointAddress("http://localhost:1924/ExecuteService.svc"); var binding = new NetHttpBinding(); var cf = new DuplexChannelFactory<IExecuteService>(new InstanceContext(this), binding, address); service = cf.CreateChannel();
L’oggetto service, definito come private della Page, è di tipo IExecuteService, ed è grazie a questo che potremo utilizzare il nostro servizio.
A questo punto aggiungiamo un Button nello Xaml, gestiamo l’evento Click, e nel code-behind inseriamo:
service.ExecuteAsync(new ExecuteRequest() { Value = 4 });
Proviamo ad eseguire l’app: è Metro-style, per cui come sappiamo che gira in full-screen. Clicchiamo sul Button. Non accade nulla. Ed è perfettamente normale, pensiamoci bene. La chiamata a Execute (che avviene in async) restituisce void. Cosa manca? Non abbiamo la callback, ovvero quel meccanismo che il server adotta per avvisarci quando lui ha terminato il calcolo. Morale: la Page deve implementare l’interfaccia di callback.
public sealed partial class MainPage : Page, IExecuteServiceCallback { public async void ExecuteTerminated(ExecuteResponse response) { MessageDialog dialog = new MessageDialog(response.Value.ToString()); await dialog.ShowAsync(); } }
Il timer, istanziato ed in esecuzione lato server, invoca la callback chiamando il metodo ExecuteTerminated sul client, e quindi apparirà la nostra MessageDialog con il risultato dell’operazione.
In ottica Model-View-ViewModel
In ottica MVVM, ovviamente, non sarà direttamente la Page ad implementare l’interfaccia IExecuteServiceCallback, ma la classe View Model che governa quella view. Il View Model gestisce la callback, lavorerà sulle proprie proprietà pubbliche per aggiornare la view.
Ciao mi è stato molto utile l’ articolo.. mi riesci a mandare un file compresso con l’ implementazione.
Thx 🙂