Windows Communication Foundation (WCF) è un "sottosistema applicativo" proprietario della Microsoft, che offre la struttura API per la creazione di applicazioni distribuite in ambienti Windows. Se da un lato ogni protocollo di rete (e.g. HTTP, FTP, SMTP, ecc.) ha un suo modello di programmazione, e necessita quindi di una conoscenza specifica da parte degli sviluppatori per poter essere utilizzata, WCF è stato realizzato con l'intento di ricondurre ad un unico modello diverse tecnologie, rendendo più semplice ed uniforme la programmazione in ambiente Windows.

Nozioni generali

Un servizio WCF si basa sugli '"EndPoint", che sono le porte attraverso le quali le applicazioni comunicano con il mondo esterno; si può quindi affermare che un servizio WCF sia una collezione di EndPoint. A sua volta, un Endpoint è costituito da quelli che sono i pilastri di WCF: "Address", "Binding", "Contract".

Cos’è l’Address

L'Address è l'indirizzo al quale il servizio risponde. L'indirizzo è composto da un URI, una "Identity" ed una lista di "Header". In fase di definizione di un Address, l'informazione principale è l'URI, che corrisponde all'indirizzo fisico del servizio. Header e Identity sono informazioni che invece sono necessarie solo in casi particolari. Ad esempio quando ci sono più EndPoint, può essere utile avere diversi Header a seconda dell'Endpoint che il client utilizza. In parole semplici si può definire l'address come il DOVE.

Cos’è il Binding

Gran parte della soluzione proposta da WCF sta nei concetto di Binding. Infatti se ci si può occupare del codice senza preoccuparsi dell'infrastruttura di trasporto lo si deve soprattutto a questa feature. I Binding si occupano di quello che avviene tra il momento in cui il servizio spedisce logicamente il messaggio ed il momento in cui viene fisicamente trasmesso in rete. In questo lasso di tempo vengono eseguiti numerosi passi che seguono una precisa pipeline di cui i binding sono responsabili. Durante l'esecuzione della pipeline il messaggio deve attraversare due livelli: il primo si occupa dei Behaviour (comportamenti), ovvero delle trasformazioni che deve subire un messaggio, il secondo si occupa dei Canali (detti Channel), ovvero dell'instradamento verso il canale fisico di trasporto. Nel primo livello ci si occupa della conversione dei dati dal formato 'codice' al formato messaggio; ad esempio, vengono trasformate le informazioni da una classe al formato XML di un messaggio SOAP. In aggiunta i Behaviour si occupano anche della sicurezza, della criptazione delle informazioni e di tutte le funzioni di gestione del dato. Durante la seconda fase il messaggio viene messo sul canale di trasporto secondo quanto specificato in fase di configurazione. Quindi è in questa fase che si instanzia il canale del protocollo originale su cui viaggeranno le informazioni. Poiché a livello di protocollo si possono controllare alcuni dettagli, in questo livello si possono aggiungere informazioni sulla modalità di trasmissione, protetta o meno, oppure sul Reliable Messaging. Come detto in precedenza; questo processo avviene per mezzo di una pipeline. Seguendo lo stesso concetto della pipeline di asp. net, possiamo vedere tutte le opzioni finora illustrate (protocollo, formattazione, ecc.) come moduli da inserire nel flusso di elaborazione del messaggio. Poiché la gestione dei Binding può essere interamente gestita in fase di configurazione, si può intuire come semplicemente cambiando poche voci si può passare dalla pubblicazione in modalità Web Service su HTTPS criptato con un certificato digitale, ad un formato ReliableMessaging senza dover modificare il codice. Se si volesse dare una parola chiave ai binding questa sarebbe COME.

Cosa sono i Contracts

I Contracts rappresentano l'interfaccia software, ovvero le API, che il nostro servizio pubblica. Poiché esistono diversi tipi di contratti e l'argomento è molto vasto è giusto creare una sezione a parte. Si può comunque dire che i Contracts siano il COSA. WCF è stato pensato sin dall'inizio tenendo in mente le architetture orientate ai servizi. In una tecnologia come questa è molto importante mettere a disposizione un'interfaccia software che l'utilizzatore del servizio ed il servizio possono utilizzare per comunicare. In WCF quest'interfaccia di scambio è il contratto. I contratti non stabiliscono solo quali operazioni si possono invocare su un servizio, ma anche come e quali dati si debbano scambiare. Da questa considerazione si evince che esistono diverse tipologie di contratti racchiuse in tre tipologie: ServiceContract, DataContract e MessageContract. I ServiceContract definiscono il servizio e tutte le API che mette a disposizione. La definizione di un simile contratto vede la sua naturale definizione in un'interfaccia .NET da implementare. Di per se, un'interfaccia non ha alcun senso parlando in termini di Service Oriented Architecture, infatti, pur definendo metodi e proprietà, non stabilisce se questi debbano essere resi pubblici o meno. Bisogna ricordare che in con SOA la visibilità di un metodo o di una proprietà all'interno di una classe, non ha alcun collegamento con la visibilità che può avere all'interno del servizio. A seconda dei casi, un metodo privato di una classe potrebbe diventare un'API del servizio, mentre un metodo pubblico della stessa classe potrebbe non essere pubblicato dal servizio. In WCF, per definire i metodi da pubblicare, bisogna decorarli con opportuni attributi dichiarati nella definizione dell'interfaccia. Gli attributi da utilizzare sono ServiceContract con la quale marcare l'interfaccia e OperationContract per ogni metodo da pubblicare. Una volta che è stato stabilito quali operazioni mettere a disposizione dei client, arriva il momento di definire quali informazioni debbano essere pubblicate. Questo è compito dei DataContract. Supponiamo che un servizio pubblichi un metodo che dato il codice fiscale ritorni i dati della persona associata. Nel DomainModel l'oggetto Persona contiene informazioni come Nome, Cognome, Data di Nascita e altro ancora, tuttavia può nascere l'esigenza di non pubblicare tutti i dati della persona ma soltanto quelli che si ritengono opportuni. Per ottenere un risultato del genere bisogna decorare sia la classe che le sue proprietà con attributi, più approfonditamente alla classe va applicato l'attributo DataContract e alle proprietà l'attributo DataMember. Per default, tutte le proprietà di una classe vengono mappate all'interno del corpo di un messaggio. A seconda delle esigenze, può capitare che si debbano piazzare delle informazioni nelle intestazioni invece che nel corpo; questo mapping tra le classi ed il formato del messaggio viene impostato tramite MessageContract. Anche in questo caso l'impostazione di queste opzioni avviene tramite attributi e, più precisamente, MessageContract per la classe e MessageBody o MessageHeader per le proprietà. Un'occhiata al codice

Esempi e dettagli tecnici

Nel seguente esempio verrà creato un servizio che riceve in input un codice prodotto e restituisce tutte le informazioni legate al prodotto con quel codice. La prima cosa da fare quando si costruisce un servizio con WCF è stabilire il contratto di comunicazione tramite interfaccia che nel nostro caso è la seguente:

[ServiceContract]
public interface IContract {
    [OperationContract]
    Product GetProductById(string IdProduct);
}

Come detto in precedenza, è importante notare l'uso degli attributi.

Una volta stabilito quali servizi mettere a disposizione, si determina quali dati l'entità Product debba pubblicare e quali invece debba mantenere privati:

[DataContract]
public class Product{
    [DataMember]
    public string IdProduct{
        get { return _idProduct; }
        set { _idProduct = value; }v }
    [DataMember]
    public string Name{
        get { return _name; }
        set { _name = value; }
    }
    [DataMember]
    private decimal Price{
        get { return _price; }
        set { _price = value; }
    }
    public string Category{
        get { return _category; }
        set { _category = value; }
    }
}

Qui la cosa principale su cui porre attenzione è la possibilità di pubblicare la proprietà Price nonostante questa sia privata. Infatti, quando il client riceverà le informazioni i dati dell'entità Product saranno tre: IdProduct, Name e Price e non Category nonostante sia pubblico. Poiché gli attributi vengono utilizzati dal runtime di WCF per serializzare i dati, questi sono il naturale contenitore di alcune proprietà che permettono di controllare questo processo. Infatti possiamo definire come chiamare l'elemento che contiene la classe o come chiamare i singoli attributi che espongono i valori delle proprietà. Possono essere definite anche altre proprietà come l'ordine, l'obbligatorietà di un dato e altro ancora. Nonostante questo buon livello di controllo a volte si ha bisogno di un controllo totale sul processo di serializzazione, in questo caso la cosa migliore è marcare l'interfaccia con l'attributo XmlSerializerFormat che invece di sfruttare il serializzatore di default utilizza il serializer standard del .NET Framework che viene utilizzato anche per i WebService Asmx. A questo punto l'interfaccia è pronta, le classi sono stabilite, manca solo l'implementazione del codice per restituire i dati:

public class ProductRetriever : IContract {
    public Product GetProductById(string IdProduct){
        Product p = new Product();
        p.IdProduct = 1;
        p.Name = 'name';
        p.Category = 'category';
        return p;
    }
}

La parte relativa al codice è finita. Per realizzare un servizio WCF questo è tutto il codice di cui abbiamo bisogno. Come si vede, non c'è alcun riferimento al tipo di tecnologia di trasmissione dati o codice di serializzazione o altro ancora. Hosting del servizio Un servizio WCF non è una vera applicazione, quindi ha bisogno di appoggiarsi ad una struttura che ne permetta la pubblicazione; in pratica il servizio ha bisogno di un Host. Si possono utilizzare diversi tipi di Host: ad esempio possiamo scrivere una Console Application, un Servizio Windows o, più semplicemente, utilizzare IIS. Quando si vuole utilizzare una propria applicazione per ospitare un servizio, occorre scrivere del codice per preparare l'infrastruttura necessaria, quindi istanziare le classi che si occupano della gestione dell'endpoint. Come già accennato, la magia di WCF sta in parte nel codice semplificato ed unificato per ogni modalità di comunicazione, e in parte nel fatto che semplicemente cambiando la configurazione si può passare da http a TCP, da messaggi in chiaro a messaggi criptati, ecc ecc. In realtà tutto quello che si può configurare da file può essere realizzato anche da codice, ma non è buona pratica cablare nell'applicazione queste informazioni. Può essere invece molto utile realizzare una form di gestione della configurazione al fine di rendere la modifica più semplice in fase di deploy. Il posto naturale dove inserire le informazioni di configurazione è il file .config all'interno del quale va inserito un nodo system.serviceModel che è il contenitore di ogni impostazione:

<configuration>
    <system.serviceModel>
        <services>
            <service name="WinFXItalia.Library.ProductRetriever">
                <endpoint
                    address="http://localhost/ws/svc"
                    binding="basicHttpBinding"
                    contract="WinFXItalia.Library.IContract" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

Dal file di configurazione possiamo ricavare tutte le informazioni necessarie per il funzionamento del servizio e utilizzarle da codice per istanziare il servizio utilizzando l'oggetto ServiceHost in una semplice console application:

using (ServiceHost serviceHost = new ServiceHost(typeof(WinFXItalia.Library.ProductRetriever), "http://localhost/ws/svc")) {
    serviceHost.Open();
}

Una volta avviata l'applicazione questa resterà in ascolto in base alle informazioni specificate nel .config relative all'address, binding e contract.

Nel caso del nostro servizio, la creazione manuale di un Host è alquanto inutile in quanto possiamo sfruttare IIS. Con l'installazione di WCF viene inserito un nuovo mapping nel metabase di IIS che associa l'estensione .SVC alla pipeline di asp.net. Grazie a questa estensione, il runtime capisce che quando arriva una richiesta con estensione SVC questa deve essere passata al motore di WCF che istanzia il servizio per soddisfare la richiesta.

La tecnica è molto simile ai file .asmx e consiste nel creare un file .svc contenente la dichiarazione del servizio da utilizzare:

<%@ServiceHost language="c#" Debug="true" Service="WinFXItalia.Library.ProductRetriever" %>

Sfruttare il servizio: il Client Quando si parla di servizi, ci sono sempre due entità: una è il servizio che, una volta avviato, rimane in attesa di essere invocato; l'altra è il client che invia messaggi al servizio.

Il client può essere una qualsiasi applicazione, sia essa WinForm, Web, un secondo servizio WCF o ovviamente una qualsiasi altra piattaforma. Per poter invocare un servizio, il client deve conoscere l'interfaccia di comunicazione che in WCF è il contratto, quindi, analogamente a quanto avviene ora per i WebService, è necessario creare un proxy che si interfacci al servizio.

Attualmente, Visual Studio non ha un'interfaccia grafica per costruire in automatico il proxy, quindi la sola possibilità è utilizzare il tool SvcUtil.exe da riga di comando. Questo tool risulta molto comodo perché non solo crea automaticamente il proxy, ma anche il file di configurazione che il client può utilizzare per la connessione. Quest'ultima frase lascia capire che anche il client può essere pilotato da configurazione senza bisogno di codice esattamente come si fa con il servizio. Avendo anche il client un EndPoint, le informazioni da pubblicare sono le stesse del servizio: l'Address, il Binding ed il Contract. I primi due corrispondono ai dati dell'endpoint utilizzato dal servizio, mentre il contract corrisponde alla classe automaticamente generata nel proxy. L'altra differenza rispetto alla configurazione del servizio è il nome del nodo principale che contiene i dati: invece di avere un nodo <services> con diversi <service> innestati, esiste un nodo client con tutti gli endpoint figli:

<configuration>
    <system.serviceModel>
        <client>
            <endpoint
                address="http://localhost/ws/svc"
                binding="basicHttpBinding"
                contract="WinFXItalia.Library.IContract"/>
        </client>
    </system.serviceModel>
</configuration>

Anche qui, una volta stabilita l'interfaccia si passa al codice per invocare il servizio e ricevere i dati. Nel nostro caso il client sarà una classica Windows Application che al click di un bottone invoca il servizio.

using (ContractProxy proxy = new ContractProxy()) {
    WinFXItalia.Library.Product p = proxy.GetProductById("1");
}

Questo snippet illustra come il codice sia in realtà molto banale e simile a quello che si fa ora per i WebService: si crea un'istanza del proxy e si invoca il metodo. Anche per il client, qualunque sia il protocollo, i Behaviours e altro ancora, sarà sempre compito di WCF eseguire la pipeline che porta alle trasformazioni e all'invio fisico del messaggio e viceversa alla ricezione della risposta, ripercorrendo la pipeline al contrario.


È stato sviluppato per Windows Vista, ma è disponibile anche per i sistemi Windows XP SP2 e Windows Server 2003.

Collegamenti esterni