Panoramica dell'architettura di Windows Communication Foundation

 

Microsoft Corporation

Marzo 2006

Riepilogo: Ottenere una panoramica generale dell'architettura di Windows Communication Foundation (WCF) e dei relativi concetti chiave. Esempi di codice illustrano contratti, endpoint e comportamenti WCF. (17 pagine stampate)

Contenuto

Introduzione
Nozioni fondamentali su WCF
Esempi di codice
Riepilogo

Introduzione

Questo documento fornisce una visualizzazione generale dell'architettura di Windows Communication Foundation (WCF). È progettato per spiegare i concetti chiave in WCF e il modo in cui interagiscono. Esistono alcuni esempi di codice per illustrare ulteriormente i concetti, ma il codice non è l'enfasi di questo documento.

Il resto di questo documento è organizzato in due sezioni principali:

  • Nozioni fondamentali su WCF: illustra i concetti chiave in WCF, termini e componenti dell'architettura.
  • Esempi di codice: fornisce alcuni brevi esempi di codice destinati a illustrare e riificare i concetti trattati in Concetti fondamentali di WCF.

Nozioni fondamentali su WCF

Un servizio WCF è un programma che espone una raccolta di endpoint. Ogni endpoint è un portale per comunicare con il mondo.

Un client è un programma che scambia messaggi con uno o più endpoint. Un client può anche esporre un endpoint per ricevere messaggi da un servizio in un modello di scambio di messaggi duplex.

Le sezioni seguenti descrivono questi concetti fondamentali in modo più dettagliato.

Endpoint

Un endpoint di servizio ha un indirizzo, un'associazione e un contratto.

L'indirizzo dell'endpoint è un indirizzo di rete in cui si trova l'endpoint. La classe EndpointAddress rappresenta un indirizzo endpoint WCF.

L'associazione dell'endpoint specifica il modo in cui l'endpoint comunica con il mondo, inclusi elementi come il protocollo di trasporto (ad esempio TCP, HTTP), la codifica (ad esempio, testo, binario) e i requisiti di sicurezza (ad esempio, SSL, sicurezza dei messaggi SOAP). La classe Binding rappresenta un'associazione WCF.

Il contratto dell'endpoint specifica ciò che l'endpoint comunica ed è essenzialmente una raccolta di messaggi organizzati in operazioni con criteri di scambio messaggi di base, ad esempio unidirezionale, duplex e richiesta/risposta. La classe ContractDescription rappresenta un contratto WCF.

La classe ServiceEndpoint rappresenta un endpoint e ha un endpointAddress, un binding e un oggetto ContractDescription corrispondente rispettivamente all'indirizzo, all'associazione e al contratto dell'endpoint (vedere la figura 1).

Aa480210.wcfarch_01(en-us,MSDN.10).gif

Figura 1. Ogni endpoint del servizio contiene un endpointAddress, un'associazione e un contratto rappresentati da ContractDescription.

EndpointAddress

EndpointAddress è fondamentalmente un URI, un'identità e una raccolta di intestazioni facoltative, come illustrato nella figura 2.

L'identità di sicurezza di un endpoint è in genere l'URI; Negli scenari avanzati, tuttavia, l'identità può essere impostata in modo esplicito indipendente dall'URI usando la proprietà Identity address.

Le intestazioni facoltative vengono usate per fornire informazioni aggiuntive sull'indirizzamento oltre l'URI dell'endpoint. Ad esempio, le intestazioni di indirizzo sono utili per distinguere tra più endpoint che condividono lo stesso URI di indirizzo.

Aa480210.wcfarch_02(en-us,MSDN.10).gif

Figura 2. EndpointAddress contiene un URI e AddressProperties contiene un'identità e una raccolta di AddressHeaders.

Associazioni

Un binding ha un nome, uno spazio dei nomi e una raccolta di elementi di associazione componibili (figura 3). Il nome e lo spazio dei nomi dell'associazione lo identificano in modo univoco nei metadati del servizio. Ogni elemento di associazione descrive un aspetto del modo in cui l'endpoint comunica con il mondo.

Aa480210.wcfarch_03(en-us,MSDN.10).gif

Figura 3. Classe di associazione e relativi membri

Ad esempio, la figura 4 mostra una raccolta di elementi di associazione contenente tre elementi di associazione. La presenza di ogni elemento di associazione descrive parte della modalità di comunicazione con l'endpoint. TcpTransportBindingElement indica che l'endpoint comunicherà con il mondo usando TCP come protocollo di trasporto. ReliableSessionBindingElement indica che l'endpoint usa la messaggistica affidabile per fornire garanzie di recapito dei messaggi. SecurityBindingElement indica che l'endpoint usa la sicurezza dei messaggi SOAP. Ogni elemento di associazione ha in genere proprietà che descrivono ulteriormente le specifiche della comunicazione con l'endpoint. Ad esempio, ReliableSessionBindingElement ha una proprietà Assurances che specifica le garanzie di recapito dei messaggi necessarie, ad esempio nessuna, almeno una volta, contemporaneamente o esattamente una volta.

Aa480210.wcfarch_04(en-us,MSDN.10).gif

Figura 4. Esempio binding con tre elementi di associazione

L'ordine e i tipi di elementi di associazione in Binding sono significativi: la raccolta di elementi di associazione viene usata per creare uno stack di comunicazioni ordinato in base all'ordine degli elementi di associazione nella raccolta di elementi di associazione. L'ultimo elemento di associazione da aggiungere alla raccolta corrisponde al componente inferiore dello stack di comunicazioni, mentre il primo corrisponde al componente superiore. I messaggi in ingresso passano attraverso lo stack verso l'alto verso l'alto, mentre i messaggi in uscita passano dall'alto verso il basso. Pertanto, l'ordine degli elementi di associazione nella raccolta influisce direttamente sull'ordine in cui i componenti dello stack di comunicazioni elaborano i messaggi. Si noti che WCF fornisce un set di associazioni predefinite che possono essere usate nella maggior parte degli scenari anziché definire associazioni personalizzate.

Contratti

Un contratto WCF è una raccolta di operazioni che specifica l'endpoint che comunica con l'esterno. Ogni operazione è un semplice scambio di messaggi, ad esempio uno scambio di messaggi unidirezionale o di richiesta/risposta.

La classe ContractDescription viene usata per descrivere i contratti WCF e le relative operazioni. All'interno di un oggetto ContractDescription, ogni operazione contract ha una corrispondente OperationDescription che descrive gli aspetti dell'operazione, ad esempio se l'operazione è unidirezionale o una richiesta/risposta. Ogni OperationDescription descrive anche i messaggi che costituiscono l'operazione usando una raccolta di MessageDescriptions.

Un oggetto ContractDescription viene in genere creato da un'interfaccia o una classe che definisce il contratto usando il modello di programmazione WCF. Questo tipo viene annotato con ServiceContractAttribute e i relativi metodi che corrispondono a Operazioni endpoint vengono annotati con OperationContractAttribute. È anche possibile compilare manualmente un oggetto ContractDescription senza iniziare con un tipo CLR annotato con attributi.

Un contratto duplex definisce due set logici di operazioni: un set esposto dal servizio affinché il client chiami e un set esposto dal client per la chiamata al servizio. Il modello di programmazione per la definizione di un contratto duplex consiste nel suddividere ogni set in un tipo separato (ogni tipo deve essere una classe o un'interfaccia) e annotare il contratto che rappresenta le operazioni del servizio con ServiceContractAttribute, facendo riferimento al contratto che definisce le operazioni client (o callback). Inoltre, ContractDescription contiene un riferimento a ognuno dei tipi, raggruppandoli in un unico contratto duplex.

Analogamente alle associazioni, ogni contratto ha un nome e uno spazio dei nomi che lo identificano in modo univoco nei metadati del servizio.

Ogni contract ha anche una raccolta di ContractBehaviors che sono moduli che modificano o estendono il comportamento del contratto. La sezione successiva illustra i comportamenti in modo più dettagliato.

Aa480210.wcfarch_05(en-us,MSDN.10).gif

Figura 5. La classe ContractDescription descrive un contratto WCF

Comportamenti

I comportamenti sono tipi che modificano o estendono la funzionalità del servizio o del client. Ad esempio, il comportamento dei metadati implementato da ServiceMetadataBehavior controlla se il servizio pubblica i metadati. Analogamente, il comportamento di sicurezza controlla la rappresentazione e l'autorizzazione, mentre il comportamento delle transazioni controlla l'integrazione nelle transazioni e il completamento automatico delle transazioni.

I comportamenti partecipano anche al processo di compilazione del canale e possono modificare tale canale in base alle impostazioni specificate dall'utente e/o ad altri aspetti del servizio o del canale.

Un comportamento del servizio è un tipo che implementa IServiceBehavior e si applica ai servizi. Analogamente, un comportamento del canale è un tipo che implementa IChannelBehavior e si applica ai canali client.

Descrizioni dei servizi e dei canali

La classe ServiceDescription è una struttura in memoria che descrive un servizio WCF, inclusi gli endpoint esposti dal servizio, i comportamenti applicati al servizio e il tipo (una classe) che implementa il servizio (vedere la figura 6). ServiceDescription viene usato per creare metadati, codice/configurazione e canali.

È possibile compilare manualmente questo oggetto ServiceDescription. È anche possibile crearlo da un tipo annotato con determinati attributi WCF, che è lo scenario più comune. Il codice per questo tipo può essere scritto manualmente o generato da un documento WSDL usando uno strumento WCF denominato svcutil.exe.

Anche se gli oggetti ServiceDescription possono essere creati e popolati in modo esplicito, vengono spesso creati in background durante l'esecuzione del servizio.

Aa480210.wcfarch_06(en-us,MSDN.10).gif

Figura 6. Modello a oggetti ServiceDescription

Analogamente sul lato client, channelDescription descrive il canale di un client WCF a un endpoint specifico (figura 7). La classe ChannelDescription ha una raccolta di IchannelBehaviors, che sono Behaviors applicate al canale. Include anche un ServiceEndpoint che descrive l'endpoint con cui il canale comunicherà.

Si noti che, a differenza di ServiceDescription, ChannelDescription contiene un solo ServiceEndpoint che rappresenta l'endpoint di destinazione con cui il canale comunicherà.

Aa480210.wcfarch_07(en-us,MSDN.10).gif

Figura 7. Modello a oggetti ChannelDescription

Runtime WCF

Wcf Runtime è il set di oggetti responsabili dell'invio e della ricezione di messaggi. Ad esempio, elementi come la formattazione dei messaggi, l'applicazione della sicurezza e la trasmissione e la ricezione di messaggi usando vari protocolli di trasporto, nonché l'invio di messaggi ricevuti all'operazione appropriata, tutti rientrano nel runtime WCF. Le sezioni seguenti illustrano i concetti chiave del runtime WCF.

Message

Il messaggio WCF è l'unità di scambio di dati tra un client e un endpoint. Un messaggio è essenzialmente una rappresentazione in memoria di un InfoSet di messaggi SOAP. Si noti che Message non è associato al testo XML. A seconda del meccanismo di codifica usato, è invece possibile serializzare un messaggio usando il formato binario WCF, il testo XML o qualsiasi altro formato personalizzato.

Canali

I canali sono l'astrazione principale per l'invio di messaggi a e la ricezione di messaggi da un endpoint. In generale, esistono due categorie di canali: i canali di trasporto gestiscono l'invio o la ricezione di flussi ottet opachi usando una forma di protocollo di trasporto, ad esempio TCP, UDP o MSMQ. I canali di protocollo, d'altra parte, implementano un protocollo basato su SOAP elaborando ed eventualmente modificando i messaggi. Ad esempio, il canale di sicurezza aggiunge ed elabora intestazioni di messaggio SOAP e può modificare il corpo del messaggio crittografandolo. I canali sono componibili in modo che un canale possa essere sovrapposto a un altro canale a sua volta sovrapposto a un terzo canale.

EndpointListener

EndpointListener è l'equivalente di runtime di serviceEndpoint. EndpointAddress, Contract e Binding di ServiceEndpoint (che rappresenta dove, cosa e come), corrispondono rispettivamente all'indirizzo di ascolto di EndpointListener, al filtro e all'invio dei messaggi e allo stack di canali. EndpointListener contiene lo stack di canali responsabile dell'invio e della ricezione di messaggi.

ServiceHost e ChannelFactory

Il runtime del servizio WCF viene in genere creato in background chiamando ServiceHost.Open. ServiceHost (figura 6) determina la creazione di un Oggetto ServiceDescription da nel tipo di servizio e popola la raccolta ServiceEndpoint di ServiceDescription con gli endpoint definiti nella configurazione o nel codice oppure entrambi. ServiceHost usa quindi ServiceDescription per creare lo stack di canali sotto forma di oggetto EndpointListener per ogni ServiceEndpoint in ServiceDescription.

Aa480210.wcfarch_08(en-us,MSDN.10).gif

Figura 8. Modello a oggetti ServiceHost

Analogamente, sul lato client, il runtime client viene creato da un ChannelFactory, che è l'equivalente del client di ServiceHost.

ChannelFactory determina la creazione di un ChannelDescription basato su un tipo di contratto, un'associazione e un endpointAddress. Usa quindi questo ChannelDescription per creare lo stack di canali del client.

A differenza del runtime del servizio, il runtime client non contiene EndpointListeners perché un client avvia sempre la connessione al servizio, quindi non è necessario "ascoltare" le connessioni in ingresso.

Esempi di codice

In questa sezione vengono forniti esempi di codice che illustrano come vengono compilati i servizi e i client. Questi esempi sono progettati per riificare i concetti precedenti e non per insegnare la programmazione WCF.

Definizione e implementazione di un contratto

Come accennato in precedenza, il modo più semplice per definire un contratto consiste nel creare un'interfaccia o una classe e annotarla con ServiceContractAttribute, consentendo al sistema di crearne facilmente una ContractDescription.

Quando si usano interfacce o classi per definire i contratti, ogni interfaccia o metodo di classe membro del contratto deve essere annotato con OperationContractAttribute. Ad esempio:

using System.ServiceModel;

//a WCF contract defined using an interface
[ServiceContract]
public interface IMath
{
    [OperationContract]
    int Add(int x, int y);
}

L'implementazione del contratto in questo caso è semplicemente una questione di creazione di una classe che implementa IMath. Tale classe diventa la classe WCF Service. Ad esempio:

//the service class implements the interface
public class MathService : IMath
{
    public int Add(int x, int y)
    { return x + y; }
}

Definizione di endpoint e avvio del servizio

Gli endpoint possono essere definiti nel codice o nella configurazione. Nell'esempio seguente il metodo DefineEndpointImperatively mostra il modo più semplice per definire gli endpoint nel codice e avviare il servizio.

Il metodo DefineEndpointInConfig mostra l'endpoint equivalente definito nella configurazione (l'esempio di configurazione segue il codice seguente).

public class WCFServiceApp
{
    public void DefineEndpointImperatively()
    {
        //create a service host for MathService
        ServiceHost sh = new ServiceHost(typeof(MathService));

        //use the AddEndpoint helper method to
        //create the ServiceEndpoint and add it 
        //to the ServiceDescription
        sh.AddServiceEndpoint(
          typeof(IMath), //contract type
          new WSHttpBinding(), //one of the built-in bindings
          "https://localhost/MathService/Ep1"); //the endpoint's address

        //create and open the service runtime
        sh.Open();

    }

    public void DefineEndpointInConfig()
    {
        //create a service host for MathService
        ServiceHost sh = new ServiceHost (typeof(MathService));

        //create and open the service runtime
        sh.Open();

    }
}
<!-- configuration file used by above code -->
<configuration 
   xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">
   <system.serviceModel>
      <services>
                 <!-- service element references the service type -->
         <service type="MathService">
            <!-- endpoint element defines the ABC's of the endpoint -->
              <endpoint 
            address="https://localhost/MathService/Ep1"
            binding="wsHttpBinding"
              contract="IMath"/>
         </service>
      </services>
   </system.serviceModel>
</configuration>

Invio di messaggi a un endpoint

Il codice seguente illustra due modi per inviare un messaggio all'endpoint IMath. SendMessageToEndpoint nasconde la creazione del canale, che avviene in background mentre l'esempio SendMessageToEndpointUsingChannel lo esegue in modo esplicito.

Il primo esempio in SendMessageToEndpoint usa uno strumento denominato svcutil.exe e i metadati del servizio per generare un oggetto Contract (IMath in questo esempio), una classe proxy (MathProxy in questo esempio) che implementa il contratto e la configurazione associata (non illustrata qui). Anche in questo caso, il contratto definito da IMath specifica gli elementi (ovvero le operazioni che possono essere eseguite), mentre la configurazione generata contiene un binding (la modalità) e un indirizzo (la posizione).

L'uso di questa classe proxy consiste semplicemente nel crearne un'istanza e chiamare il metodo Add . Dietro le quinte, la classe proxy creerà un canale e lo userà per comunicare con l'endpoint.

Il secondo esempio in SendMessageToEndpointsUsingChannel seguente mostra la comunicazione diretta con un endpoint tramite ChannelFactory. In questo esempio, invece di usare una classe proxy e una configurazione, viene creato un canale direttamente usando ChannelFactory<IMath>. CreateChannel. Invece di usare la configurazione per definire l'indirizzo e l'associazione dell'endpoint, il costruttore IMath> ChannelFactory< accetta anche queste due informazioni come parametri. Il terzo elemento di informazioni necessario per definire un endpoint, ovvero il contratto, viene passato come tipo T.

using System.ServiceModel;
//this contract is generated by svcutil.exe
//from the service's metadata
public interface IMath
{
    [OperationContract]
    public int Add(int x, int y)
    { return x + y; }
}


//this class is generated by svcutil.exe
//from the service's metadata
//generated config is not shown here
public class MathProxy : IMath
{
    ...
}

public class WCFClientApp
{
    public void SendMessageToEndpoint()
    {
        //this uses a proxy class that was
        //created by svcutil.exe from the service's metadata
        MathProxy proxy = new MathProxy();

        int result = proxy.Add(35, 7);
    }
    public void SendMessageToEndpointUsingChannel()
    {
        //this uses ChannelFactory to create the channel
        //you must specify the address, the binding and 
        //the contract type (IMath)
        ChannelFactory<IMath> factory=new ChannelFactory<IMath>(
            new WSHttpBinding(),
            new EndpointAddress("https://localhost/MathService/Ep1"));
        IMath channel=factory.CreateChannel();
        int result=channel.Add(35,7);
        factory.Close();

    }
}

Definizione di un comportamento personalizzato

La definizione di un comportamento personalizzato è una questione di implementazione di IServiceBehavior (o IChannelBehavior per i comportamenti lato client). Il codice seguente mostra un comportamento di esempio che implementa IServiceBehavior. In IServiceBehavior.ApplyBehavior il codice controlla serviceDescription e scrive l'indirizzo, l'associazione e il contratto di ogni ServiceEndpoint, nonché il nome di ogni comportamento in ServiceDescription.

Questo particolare comportamento è anche un attributo (eredita da System.Attribute), rendendo possibile applicare in modo dichiarativo come illustrato di seguito. Tuttavia, non è necessario che i comportamenti siano attributi.

[AttributeUsageAttribute(
         AttributeTargets.Class,
         AllowMultiple=false,
         Inherited=false)]
public class InspectorBehavior : System.Attribute, 
                                 System.ServiceModel.IServiceBehavior
{
   public void ApplyBehavior(
       ServiceDescription description,
       Collection<DispatchBehavior> behaviors)
   {
       Console.WriteLine("-------- Endpoints ---------");
       foreach (ServiceEndpoint endpoint in description.Endpoints)
       {
           Console.WriteLine("--> Endpoint");
           Console.WriteLine("Endpoint Address: {0}", 
                             endpoint.Address);
           Console.WriteLine("Endpoint Binding: {0}", 
                             endpoint.Binding.GetType().Name);
           Console.WriteLine("Endpoint Contract: {0}", 
                              endpoint.Contract.ContractType.Name);
           Console.WriteLine();
       }
       Console.WriteLine("-------- Service Behaviors --------");
       foreach (IServiceBehavior behavior in description.Behaviors)
       {
           Console.WriteLine("--> Behavior");
           Console.WriteLine("Behavior: {0}", behavior.GetType().Name);
           Console.WriteLine();
       }
   }
}

Applicazione di un comportamento personalizzato

Tutti i comportamenti possono essere applicati in modo imperativo aggiungendo un'istanza del comportamento a ServiceDescription (o ChannelDescription sul lato client). Ad esempio, per applicare l'oggetto InspectorBehavior in modo imperativo, è necessario scrivere:

ServiceHost sh = new ServiceHost(typeof(MathService));
sh.AddServiceEndpoint(
       typeof(IMath), 
        new WSHttpBinding(),
       "https://localhost/MathService/Ep1"); 
//Add the behavior imperativelyInspectorBehavior behavior = new InspectorBehavior();sh.Description.Behaviors.Add(behavior);
sh.Open();

Inoltre, i comportamenti che ereditano da System.Attribute possono essere applicati in modo dichiarativo al servizio. Ad esempio, poiché InspectorBehavior eredita da System.Attribute, può essere applicato in modo dichiarativo come segue:

[InspectorBehavior]
public class MathService : IMath
{
   public int Add(int x, int y)
   { return x + y; }
}

Riepilogo

I servizi WCF espongono una raccolta di endpoint in cui ogni endpoint è un portale per comunicare con il mondo. Ogni endpoint ha un indirizzo, un'associazione e un contratto (ABC). L'indirizzo è la posizione in cui risiede l'endpoint, l'associazione è il modo in cui comunica l'endpoint e il contratto è ciò che comunica l'endpoint.

Nel servizio, un Oggetto ServiceDescription contiene la raccolta di ServiceEndpoints ognuna che descrive un endpoint esposto dal servizio. Da questa descrizione ServiceHost crea un runtime che contiene un EndpointListener per ogni ServiceEndpoint in ServiceDescription. L'indirizzo, l'associazione e il contratto dell'endpoint (che rappresentano la posizione, la posizione e la modalità) corrispondono rispettivamente all'indirizzo di ascolto di EndpointListener, al filtro dei messaggi e all'invio e allo stack di canali.

Analogamente, nel client un ChannelDescription contiene l'unico ServiceEndpoint con cui comunica il client. Da questo ChannelDescription, ChannelFactory crea lo stack di canali in grado di comunicare con l'endpoint del servizio.