Condividi tramite


Progettazione di contratti di servizio

Questo argomento descrive quali contratti di servizio sono, come vengono definiti, quali operazioni sono disponibili (e le implicazioni per gli scambi di messaggi sottostanti), quali tipi di dati vengono usati e altri problemi che consentono di progettare operazioni che soddisfano i requisiti dello scenario.

Creazione di un contratto di servizio

I servizi espongono una serie di operazioni. Nelle applicazioni Windows Communication Foundation (WCF) definire le operazioni creando un metodo e contrassegnandolo con l'attributo OperationContractAttribute . Quindi, per creare un contratto di servizio, raggruppare le operazioni dichiarandole all'interno di un'interfaccia contrassegnata con l'attributo ServiceContractAttribute o definendole in una classe contrassegnata con lo stesso attributo. Per un esempio di base, vedere Procedura: Definire un contratto di servizio.

Tutti i metodi che non dispongono di un OperationContractAttribute attributo non sono operazioni del servizio e non vengono esposti dai servizi WCF.

In questo argomento vengono descritti i punti decisionali seguenti durante la progettazione di un contratto di servizio:

  • Indica se usare classi o interfacce.

  • Come specificare i tipi di dati da scambiare.

  • Tipi di modelli di scambio che è possibile usare.

  • Indica se è possibile impostare requisiti di sicurezza espliciti come parte del contratto.

  • Restrizioni per gli input e gli output dell'operazione.

Classi o interfacce

Entrambe le classi e le interfacce rappresentano un raggruppamento di funzionalità e pertanto entrambi possono essere usati per definire un contratto di servizio WCF. È tuttavia consigliabile usare le interfacce perché modellano direttamente i contratti di servizio. Senza un'implementazione, le interfacce non definiscono più di un raggruppamento di metodi con determinate firme. Implementa un'interfaccia del contratto di servizio e avrai implementato un servizio WCF.

Tutti i vantaggi delle interfacce gestite si applicano alle interfacce del contratto di servizio:

  • Le interfacce del contratto di servizio possono estendere qualsiasi numero di altre interfacce del contratto di servizio.

  • Una singola classe può implementare un numero qualsiasi di contratti di servizio implementando tali interfacce del contratto di servizio.

  • È possibile modificare l'implementazione di un contratto di servizio modificando l'implementazione dell'interfaccia, mentre il contratto di servizio rimane invariato.

  • È possibile eseguire la versione del servizio implementando l'interfaccia precedente e quella nuova. I client precedenti si connettono alla versione originale, mentre i client più recenti possono connettersi alla versione più recente.

Annotazioni

Quando eredita da altre interfacce del contratto di servizio, non è possibile eseguire l'override delle proprietà dell'operazione, ad esempio il nome o lo spazio dei nomi. Se si tenta di eseguire questa operazione, si crea una nuova operazione nel contratto di servizio corrente.

Per un esempio di utilizzo di un'interfaccia per creare un contratto di servizio, vedere Procedura: Creare un servizio con un'interfaccia del contratto.

È tuttavia possibile usare una classe per definire un contratto di servizio e implementare tale contratto contemporaneamente. Il vantaggio di creare i servizi applicando ServiceContractAttribute e OperationContractAttribute direttamente alla classe e ai metodi della classe, rispettivamente, è velocità e semplicità. Gli svantaggi sono che le classi gestite non supportano più ereditarietà e di conseguenza possono implementare un solo contratto di servizio alla volta. Inoltre, qualsiasi modifica alle firme della classe o del metodo modifica il contratto pubblico per tale servizio, che può impedire ai client non modificati di usare il servizio. Per altre informazioni, vedere Implementazione dei contratti di servizio.

Per un esempio che usa una classe per creare un contratto di servizio e la implementa contemporaneamente, vedere Procedura: Creare un servizio con una classe contract.

A questo punto, è necessario comprendere la differenza tra la definizione del contratto di servizio usando un'interfaccia e usando una classe . Il passaggio successivo consiste nel decidere quali dati possono essere passati tra un servizio e i relativi client.

Parametri e valori restituiti

Ogni operazione ha un valore restituito e un parametro, anche se sono void. Tuttavia, a differenza di un metodo locale, in cui è possibile passare riferimenti a oggetti da un oggetto a un altro, le operazioni del servizio non passano riferimenti agli oggetti. Invece, passano copie degli oggetti.

Questo è significativo perché ogni tipo usato in un parametro o un valore restituito deve essere serializzabile; ovvero, deve essere possibile convertire un oggetto di tale tipo in un flusso di byte e da un flusso di byte in un oggetto .

I tipi primitivi sono serializzabili per impostazione predefinita, come molti tipi in .NET Framework.

Annotazioni

Il valore dei nomi dei parametri nella firma dell'operazione fa parte del contratto e fa distinzione tra maiuscole e minuscole. Per usare lo stesso nome di parametro in locale, ma modificare il nome nei metadati pubblicati, vedere System.ServiceModel.MessageParameterAttribute.

Contratti dati

Le applicazioni orientate ai servizi come le applicazioni Windows Communication Foundation (WCF) sono progettate per interagire con il maggior numero possibile di applicazioni client su piattaforme Microsoft e non Microsoft. Per l'interoperabilità più ampia possibile, è consigliabile contrassegnare i tipi con gli DataContractAttribute attributi e DataMemberAttribute per creare un contratto dati, ovvero la parte del contratto di servizio che descrive i dati scambiati dalle operazioni del servizio.

I contratti dati sono contratti di tipo opt-in: nessun tipo o membro di dati viene serializzato a meno che non si applichi l'attributo del contratto dati. I contratti di dati non sono correlati all'ambito di accesso del codice gestito: i membri dati privati possono essere serializzati e inviati altrove, dove possono essere accessibili pubblicamente. Per un esempio di base di un contratto dati, vedere Procedura: Creare un contratto dati di base per una classe o una struttura. WCF gestisce la definizione dei messaggi SOAP sottostanti che consentono la funzionalità dell'operazione, nonché la serializzazione dei tipi di dati all'interno e all'esterno del corpo dei messaggi. Se i tipi di dati sono serializzabili, non è necessario considerare l'infrastruttura di scambio di messaggi sottostante durante la progettazione delle operazioni.

Anche se l'applicazione WCF tipica usa gli DataContractAttribute attributi e DataMemberAttribute per creare contratti dati per le operazioni, è possibile usare altri meccanismi di serializzazione. I meccanismi standard ISerializable, SerializableAttributee IXmlSerializable funzionano tutti per gestire la serializzazione dei tipi di dati nei messaggi SOAP sottostanti che li trasportano da un'applicazione a un'altra. Se i tipi di dati richiedono un supporto speciale, è possibile adottare più strategie di serializzazione. Per altre informazioni sulle scelte per la serializzazione dei tipi di dati nelle applicazioni WCF, vedere Specifica del trasferimento dei dati nei contratti di servizio.

Mappatura dei parametri e dei valori restituiti agli scambi di messaggi

Le operazioni del servizio sono supportate da uno scambio sottostante di messaggi SOAP che trasferiscono i dati dell'applicazione in avanti e indietro, oltre ai dati richiesti dall'applicazione per supportare determinate funzionalità standard di sicurezza, transazione e sessione. Poiché questo è il caso, la firma di un'operazione del servizio determina un determinato modello di scambio di messaggi sottostante (MEP) in grado di supportare il trasferimento dei dati e le funzionalità richieste da un'operazione. È possibile specificare tre modelli nel modello di programmazione WCF: request/reply, unidirezionale e modelli di messaggio duplex.

Richiesta/risposta

Un modello di richiesta/risposta è uno in cui un mittente di richiesta (un'applicazione client) riceve una risposta con cui la richiesta è correlata. Si tratta del mep predefinito perché supporta un'operazione in cui uno o più parametri vengono passati all'operazione e un valore restituito viene passato al chiamante. Nell'esempio di codice C# seguente, ad esempio, viene illustrata un'operazione di base del servizio che accetta una stringa e restituisce una stringa.

[OperationContractAttribute]
string Hello(string greeting);

Di seguito è riportato il codice Visual Basic equivalente.

<OperationContractAttribute()>
Function Hello (ByVal greeting As String) As String

Questa firma dell'operazione determina la forma dello scambio di messaggi sottostante. Se non esiste alcuna correlazione, WCF non può determinare per quale operazione è previsto il valore restituito.

Si noti che, a meno che non si specifichi un modello di messaggio sottostante diverso, anche le operazioni del servizio che restituiscono void (Nothing in Visual Basic) sono scambi di messaggi di richiesta/risposta. Il risultato dell'operazione è che, a meno che un client non richiami l'operazione in modo asincrono, il client interrompe l'elaborazione fino alla ricezione del messaggio restituito, anche se tale messaggio è vuoto nel caso normale. L'esempio di codice C# seguente mostra un'operazione che non restituisce finché il client non ha ricevuto un messaggio vuoto in risposta.

[OperationContractAttribute]
void Hello(string greeting);

Di seguito è riportato il codice Visual Basic equivalente.

<OperationContractAttribute()>
Sub Hello (ByVal greeting As String)

L'esempio precedente può rallentare le prestazioni e la velocità di risposta del client se l'operazione richiede molto tempo, ma esistono vantaggi per le operazioni di richiesta/risposta anche quando restituiscono void. Il più ovvio è che gli errori SOAP possono essere restituiti nel messaggio di risposta, che indica che si è verificata una condizione di errore correlata al servizio, sia nella comunicazione che nell'elaborazione. Gli errori SOAP specificati in un contratto di servizio vengono passati all'applicazione client come FaultException<TDetail> oggetto, in cui il parametro di tipo è il tipo specificato nel contratto di servizio. In questo modo, notificare ai client le condizioni di errore nei servizi WCF diventa facile. Per altre informazioni su eccezioni, errori SOAP e gestione degli errori, vedere Impostazione e gestione degli errori in Contratti e servizi. Per un esempio di servizio di richiesta/risposta e client, vedere Procedura: Creare un contratto Request-Reply. Per altre informazioni sui problemi relativi al modello request-reply, vedere Request-Reply Services.

Unidirezionale

Se il client di un'applicazione di servizio WCF non deve attendere il completamento dell'operazione e non elabora errori SOAP, l'operazione può specificare un modello di messaggio unidirezionale. Un'operazione unidirezionale è quella in cui un client richiama un'operazione e continua l'elaborazione dopo che WCF scrive il messaggio nella rete. In genere ciò significa che, a meno che i dati inviati nel messaggio in uscita siano estremamente grandi, il client continua a essere in esecuzione quasi immediatamente (a meno che non si verifichi un errore durante l'invio dei dati). Questo tipo di modello di scambio di messaggi supporta il comportamento simile a un evento da un client a un'applicazione di servizio.

Uno scambio di messaggi in cui viene inviato un messaggio e nessuno viene ricevuto non può supportare un'operazione del servizio che specifica un valore restituito diverso da void. In questo caso viene generata un'eccezione InvalidOperationException .

Nessun messaggio restituito significa anche che non può essere restituito alcun errore SOAP per indicare eventuali errori nell'elaborazione o nella comunicazione. La comunicazione delle informazioni sugli errori quando le operazioni sono operazioni unidirezionali richiede un modello di scambio di messaggi duplex.

Per specificare uno scambio di messaggi unidirezionale per un'operazione che restituisce void, impostare la IsOneWay proprietà su true, come nell'esempio di codice C# seguente.

[OperationContractAttribute(IsOneWay=true)]
void Hello(string greeting);

Di seguito è riportato il codice Visual Basic equivalente.

<OperationContractAttribute(IsOneWay := True)>
Sub Hello (ByVal greeting As String)

Questo metodo è identico all'esempio di richiesta/risposta precedente, ma impostando la IsOneWay proprietà su true significa che, sebbene il metodo sia identico, l'operazione del servizio non invia un messaggio restituito e i client restituiscono immediatamente una volta che il messaggio in uscita è stato passato al livello del canale. Per un esempio, vedere Procedura: Creare un contratto One-Way. Per altre informazioni sul modello unidirezionale, vedere One-Way Services.

Duplex

Un modello duplex è caratterizzato dalla capacità sia del servizio che del client di inviare messaggi l'uno all'altro indipendentemente dall'uso della messaggistica unidirezionale o richiesta/risposta. Questa forma di comunicazione bidirezionale è utile per i servizi che devono comunicare direttamente con il client o per fornire un'esperienza asincrona a entrambi i lati di uno scambio di messaggi, incluso il comportamento simile a un evento.

Il modello duplex è leggermente più complesso rispetto ai modelli request/reply o unidirezionale a causa del meccanismo aggiuntivo per la comunicazione con il client.

Per progettare un contratto duplex, è inoltre necessario progettare un contratto di callback e assegnare il tipo di tale contratto di callback alla CallbackContract proprietà dell'attributo ServiceContractAttribute che contrassegna il contratto di servizio.

Per implementare un modello duplex, è necessario creare una seconda interfaccia contenente le dichiarazioni di metodo chiamate nel client.

Per un esempio di creazione di un servizio e di un client che accede a tale servizio, vedere Procedura: Creare un contratto duplex e Procedura: Accedere ai servizi con un contratto duplex. Per un esempio funzionante, vedere Duplex. Per altre informazioni sui problemi relativi all'uso di contratti duplex, vedere Servizi duplex.

Attenzione

Quando un servizio riceve un messaggio duplex, esamina l'elemento ReplyTo nel messaggio in arrivo per determinare dove inviare la risposta. Se il canale usato per ricevere il messaggio non è protetto, un client non attendibile potrebbe inviare un messaggio dannoso con il computer di ReplyTodestinazione, causando un denial of service (DOS) del computer di destinazione.

Parametri Out e Ref

Nella maggior parte dei casi, è possibile usare in parametri (ByVal in Visual Basic) e outref parametri (ByRef in Visual Basic). Poiché entrambi i parametri out e ref indicano che i dati vengono restituiti da un'operazione, una firma dell'operazione come nel seguente esempio specifica che è necessaria un'operazione di richiesta/risposta, anche se la firma restituisce void.

[ServiceContractAttribute]
public interface IMyContract
{
  [OperationContractAttribute]
  public void PopulateData(ref CustomDataType data);
}

Di seguito è riportato il codice Visual Basic equivalente.

<ServiceContractAttribute()> _
Public Interface IMyContract
  <OperationContractAttribute()> _
  Public Sub PopulateData(ByRef data As CustomDataType)
End Interface

Le uniche eccezioni sono i casi in cui la firma ha una struttura specifica. Ad esempio, è possibile usare l'associazione NetMsmqBinding per comunicare con i client solo se il metodo usato per dichiarare un'operazione restituisce void. Non può essere presente alcun valore di output, indipendentemente dal fatto che si tratti di un valore restituito, refo out di un parametro.

Inoltre, l'uso di out parametri o ref richiede che l'operazione disponga di un messaggio di risposta sottostante per riportare l'oggetto modificato. Se l'operazione è un'operazione unidirezionale, viene generata un'eccezione InvalidOperationException in fase di esecuzione.

Specificare il livello di protezione dei messaggi nel contratto

Quando si progetta il contratto, è necessario decidere anche il livello di protezione dei messaggi dei servizi che implementano il contratto. Questa operazione è necessaria solo se la sicurezza dei messaggi viene applicata al binding nel'endpoint del contratto. Se l'associazione ha la sicurezza disattivata (ossia, se l'associazione fornita dal sistema imposta il System.ServiceModel.SecurityMode al valore SecurityMode.None) non è necessario decidere il livello di protezione dei messaggi per il contratto. Nella maggior parte dei casi, le associazioni fornite dal sistema con sicurezza a livello di messaggio applicate forniscono un livello di protezione sufficiente e non è necessario considerare il livello di protezione per ogni operazione o per ogni messaggio.

Il livello di protezione è un valore che specifica se i messaggi (o parti di messaggio) che supportano un servizio sono firmati, firmati e crittografati o inviati senza firme o crittografia. Il livello di protezione può essere impostato a vari ambiti: a livello di servizio, per una particolare operazione, per un messaggio all'interno di tale operazione o una parte del messaggio. I valori impostati in un ambito diventano il valore predefinito per gli ambiti più piccoli, a meno che non venga eseguito l'override esplicito. Se una configurazione di associazione non è in grado di fornire il livello minimo di protezione richiesto per il contratto, viene generata un'eccezione. E quando non vengono impostati valori del livello di protezione in modo esplicito nel contratto, la configurazione dell'associazione controlla il livello di protezione per tutti i messaggi se l'associazione dispone di sicurezza dei messaggi. Si tratta del comportamento predefinito.

Importante

Decidere se impostare esplicitamente vari ambiti di un contratto a meno del livello di protezione completo di ProtectionLevel.EncryptAndSign è generalmente una decisione che scambia un certo grado di sicurezza per un miglioramento delle prestazioni. In questi casi, le decisioni devono ruotare intorno alle operazioni e al valore dei dati che scambiano. Per altre informazioni, vedere Protezione dei servizi.

Ad esempio, il seguente esempio di codice non imposta né la proprietà ProtectionLevel né la proprietà ProtectionLevel sul contratto.

[ServiceContract]
public interface ISampleService
{
  [OperationContractAttribute]
  public string GetString();

  [OperationContractAttribute]
  public int GetInt();
}

Di seguito è riportato il codice Visual Basic equivalente.

<ServiceContractAttribute()> _
Public Interface ISampleService

  <OperationContractAttribute()> _
  Public Function GetString()As String

  <OperationContractAttribute()> _
  Public Function GetData() As Integer

End Interface

Quando si interagisce con un'implementazione ISampleService in un endpoint con un valore predefinito (il valore predefinito WSHttpBindingSystem.ServiceModel.SecurityMode, ovvero Message), tutti i messaggi vengono crittografati e firmati perché si tratta del livello di protezione predefinito. Tuttavia, quando un ISampleService servizio viene usato con un valore predefinito (il valore predefinito BasicHttpBindingSecurityMode, ovvero None), tutti i messaggi vengono inviati come testo perché non esiste alcuna sicurezza per questa associazione e quindi il livello di protezione viene ignorato (ovvero i messaggi non sono crittografati né firmati). Se l'oggetto SecurityMode è stato modificato in Message, questi messaggi verranno crittografati e firmati (perché questo sarebbe ora il livello di protezione predefinito dell'associazione).

Se desideri specificare o modificare in modo esplicito i requisiti di protezione per il tuo contratto, imposta la ProtectionLevel proprietà (o una delle ProtectionLevel proprietà a un ambito più ristretto) al livello richiesto dal contratto di servizio. In questo caso, l'uso di un'impostazione esplicita richiede l'associazione per supportare tale impostazione almeno per l'ambito usato. Nell'esempio di codice seguente, ad esempio, viene specificato un ProtectionLevel valore in modo esplicito per l'operazione GetGuid .

[ServiceContract]
public interface IExplicitProtectionLevelSampleService
{
  [OperationContractAttribute]
  public string GetString();

  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.None)]
  public int GetInt();
  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.EncryptAndSign)]
  public int GetGuid();
}

Di seguito è riportato il codice Visual Basic equivalente.

<ServiceContract()> _
Public Interface IExplicitProtectionLevelSampleService
    <OperationContract()> _
    Public Function GetString() As String
    End Function

    <OperationContract(ProtectionLevel := ProtectionLevel.None)> _
    Public Function GetInt() As Integer
    End Function

    <OperationContractAttribute(ProtectionLevel := ProtectionLevel.EncryptAndSign)> _
    Public Function GetGuid() As Integer
    End Function

End Interface

Un servizio che implementa questo IExplicitProtectionLevelSampleService contratto e ha un endpoint che utilizza l'impostazione predefinita (WSHttpBinding, il default System.ServiceModel.SecurityMode, ovvero Message) presenta il seguente comportamento:

  • I GetString messaggi dell'operazione vengono crittografati e firmati.

  • I GetInt messaggi dell'operazione vengono inviati come testo non crittografato e senza segno (ovvero normale).

  • L'operazione GetGuidSystem.Guid viene restituita in un messaggio crittografato e firmato.

Per altre informazioni sui livelli di protezione e su come usarli, vedere Informazioni sul livello di protezione. Per altre informazioni sulla sicurezza, vedere Protezione dei servizi.

Altri requisiti di firma dell'operazione

Alcune funzionalità dell'applicazione richiedono un particolare tipo di firma operativa. Ad esempio, l'associazione NetMsmqBinding supporta i servizi durevoli e i client, in cui un'applicazione può essere riavviata a metà della comunicazione e riprendere da dove era stata interrotta senza perdere alcun messaggio. Per altre informazioni, vedere Code in WCF. Tuttavia, le operazioni durevoli devono accettare un solo parametro in e non avere alcun valore restituito.

Un altro esempio è l'uso dei Stream tipi nelle operazioni. Poiché il Stream parametro include l'intero corpo del messaggio, se un input o un output , ovvero ref parametro, out parametro o valore restituito, è di tipo Stream, deve essere l'unico input o output specificato nell'operazione. Inoltre, il parametro o il tipo restituito deve essere Stream, System.ServiceModel.Channels.Messageo System.Xml.Serialization.IXmlSerializable. Per altre informazioni sui flussi, vedere Dati di grandi dimensioni e streaming.

Nomi, spazi dei nomi e offuscamento

I nomi e gli spazi dei nomi dei tipi .NET nella definizione di contratti e operazioni sono significativi quando i contratti vengono convertiti in WSDL e quando vengono creati e inviati messaggi di contratto. È pertanto fortemente consigliato impostare in modo esplicito nomi e spazi dei nomi dei contratti di servizio utilizzando le proprietà Name e Namespace di tutti gli attributi del contratto di supporto, come ServiceContractAttribute, OperationContractAttribute, DataContractAttribute, DataMemberAttribute e altri attributi del contratto.

Un risultato è che se i nomi e gli spazi dei nomi non sono impostati in modo esplicito, l'uso dell'offuscamento IL nell'assembly modifica i nomi e gli spazi dei nomi dei tipi di contratto e genera scambi WSDL e wire modificati che in genere hanno esito negativo. Se non si impostano i nomi dei contratti e gli spazi dei nomi in modo esplicito, ma si intende usare l'offuscamento, utilizzare gli attributi ObfuscationAttribute e ObfuscateAssemblyAttribute per impedire la modifica dei nomi dei tipi di contratto e degli spazi dei nomi.

Vedere anche