Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Windows Communication Foundation (WCF) può essere considerato come un'infrastruttura di messaggistica. Le operazioni del servizio possono ricevere messaggi, elaborarli e inviarli. I messaggi vengono descritti usando i contratti di operazione. Si consideri ad esempio il contratto seguente.
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(string fromCity, string toCity);
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
End Interface
In questo caso, l'operazione GetAirfare accetta un messaggio con informazioni su fromCity e toCitye quindi restituisce un messaggio che contiene un numero.
In questo argomento vengono illustrati i vari modi in cui un contratto di operazione può descrivere i messaggi.
Descrizione dei messaggi tramite parametri
Il modo più semplice per descrivere un messaggio consiste nell'usare un elenco di parametri e il valore restituito. Nell'esempio precedente sono stati usati i fromCity parametri stringa e toCity per descrivere il messaggio di richiesta e il valore restituito float è stato usato per descrivere il messaggio di risposta. Se il valore restituito da solo non è sufficiente per descrivere un messaggio di risposta, è possibile usare parametri out. Ad esempio, l'operazione seguente include fromCity e toCity nel messaggio di richiesta e un numero insieme a una valuta nel messaggio di risposta:
[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
Inoltre, è possibile usare parametri di riferimento per fare parte di un parametro sia della richiesta che del messaggio di risposta. I parametri devono essere di tipi che possono essere serializzati (convertiti in XML). Per impostazione predefinita, WCF usa un componente denominato DataContractSerializer classe per eseguire questa conversione. La maggior parte dei tipi primitivi ( ad esempio int, stringfloat, e DateTime. ) è supportata. I tipi definiti dall'utente devono in genere avere un contratto dati. Per altre informazioni, vedere Uso dei contratti dati.
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(Itinerary itinerary, DateTime date);
[DataContract]
public class Itinerary
{
[DataMember]
public string fromCity;
[DataMember]
public string toCity;
}
}
Public Interface IAirfareQuoteService
<OperationContract()>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
<DataContract()>
Class Itinerary
<DataMember()>
Public fromCity As String
<DataMember()>
Public toCity As String
End Class
End Interface
In alcuni casi, il DataContractSerializer non è adeguato per serializzare i tuoi tipi. WCF supporta un motore di serializzazione alternativo, , XmlSerializerche è anche possibile usare per serializzare i parametri.
XmlSerializer consente di usare un maggiore controllo sul codice XML risultante usando attributi come XmlAttributeAttribute. Per passare all'uso XmlSerializer di per un'operazione specifica o per l'intero servizio, applicare l'attributo XmlSerializerFormatAttribute a un'operazione o a un servizio. Per esempio:
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
[XmlSerializerFormat]
float GetAirfare(Itinerary itinerary, DateTime date);
}
public class Itinerary
{
public string fromCity;
public string toCity;
[XmlAttribute]
public bool isFirstClass;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
<XmlSerializerFormat>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
End Interface
Class Itinerary
Public fromCity As String
Public toCity As String
<XmlSerializerFormat()>
Public isFirstClass As Boolean
End Class
Per altre informazioni, vedere Uso della classe XmlSerializer. Tenere presente che il passaggio manuale a XmlSerializer come illustrato di seguito non è consigliato, a meno che non si disponga di motivi specifici per farlo, come descritto in questo argomento.
Per isolare i nomi dei parametri .NET dai nomi dei contratti, è possibile usare l'attributo MessageParameterAttribute e usare la Name proprietà per impostare il nome del contratto. Ad esempio, il contratto dell'operazione seguente equivale al primo esempio di questo argomento.
[OperationContract]
public float GetAirfare(
[MessageParameter(Name="fromCity")] string originCity,
[MessageParameter(Name="toCity")] string destinationCity);
<OperationContract()>
Function GetAirfare(<MessageParameter(Name := "fromCity")> fromCity As String, <MessageParameter(Name := "toCity")> toCity As String) As Double
Descrizione di messaggi vuoti
Un messaggio di richiesta vuoto può essere descritto senza parametri di input o riferimento. Ad esempio, in C#:
[OperationContract]
public int GetCurrentTemperature();
Ad esempio, in Visual Basic:
<OperationContract()>
Function GetCurrentTemperature() as Integer
Un messaggio di risposta vuoto può essere descritto con un void tipo restituito e senza parametri di output o riferimento. Ad esempio, in:
[OperationContract]
public void SetTemperature(int temperature);
<OperationContract()>
Sub SetTemperature(temperature As Integer)
Questa operazione è diversa da un'operazione unidirezionale, ad esempio:
[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);
<OperationContract(IsOneWay:=True)>
Sub SetLightbulbStatus(isOne As Boolean)
L'operazione SetTemperatureStatus restituisce un messaggio vuoto. Potrebbe invece restituire un errore se si verifica un problema durante l'elaborazione del messaggio di input. L'operazione SetLightbulbStatus non restituisce nulla. Non è possibile comunicare una condizione di errore da questa operazione.
Descrizione dei messaggi tramite contratti di messaggio
È possibile usare un singolo tipo per rappresentare l'intero messaggio. Sebbene sia possibile usare un contratto dati a questo scopo, è consigliabile usare un contratto di messaggio, evitando così livelli inutili di wrapping nel codice XML risultante. Inoltre, i contratti di messaggio consentono di esercitare un maggiore controllo sui messaggi risultanti. Ad esempio, è possibile decidere quali informazioni devono trovarsi nel corpo del messaggio e quali devono trovarsi nelle intestazioni del messaggio. Nell'esempio seguente viene illustrato l'uso di contratti di messaggio.
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
GetAirfareResponse GetAirfare(GetAirfareRequest request);
}
[MessageContract]
public class GetAirfareRequest
{
[MessageHeader] public DateTime date;
[MessageBodyMember] public Itinerary itinerary;
}
[MessageContract]
public class GetAirfareResponse
{
[MessageBodyMember] public float airfare;
[MessageBodyMember] public string currency;
}
[DataContract]
public class Itinerary
{
[DataMember] public string fromCity;
[DataMember] public string toCity;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(request As GetAirfareRequest) As GetAirfareResponse
End Interface
<MessageContract()>
Public Class GetAirfareRequest
<MessageHeader()>
Public Property date as DateTime
<MessageBodyMember()>
Public Property itinerary As Itinerary
End Class
<MessageContract()>
Public Class GetAirfareResponse
<MessageBodyMember()>
Public Property airfare As Double
<MessageBodyMember()> Public Property currency As String
End Class
<DataContract()>
Public Class Itinerary
<DataMember()> Public Property fromCity As String
<DataMember()> Public Property toCity As String
End Class
Per altre informazioni, vedere Uso dei contratti di messaggio.
Nell'esempio precedente la DataContractSerializer classe viene ancora usata per impostazione predefinita. La XmlSerializer classe può essere usata anche con contratti di messaggio. A tale scopo, applicare l'attributo XmlSerializerFormatAttribute all'operazione o al contratto e usare tipi compatibili con la classe XmlSerializer nelle intestazioni del messaggio e nei membri del corpo.
Descrizione dei messaggi tramite flussi
Un altro modo per descrivere i messaggi nelle operazioni consiste nell'usare la Stream classe o una delle relative classi derivate in un contratto dell'operazione o come membro del corpo del contratto del messaggio (in questo caso deve essere l'unico membro). Per i messaggi in arrivo, il tipo deve essere Stream: non è possibile usare classi derivate.
Anziché richiamare il serializzatore, WCF recupera i dati da un flusso e lo inserisce direttamente in un messaggio in uscita oppure recupera i dati da un messaggio in ingresso e lo inserisce direttamente in un flusso. L'esempio seguente illustra l'uso dei flussi.
[OperationContract]
public Stream DownloadFile(string fileName);
<OperationContract()>
Function DownloadFile(fileName As String) As String
Non è possibile combinare Stream e non trasmettere i dati in un singolo corpo del messaggio. Usare un contratto di messaggio per inserire i dati aggiuntivi nelle intestazioni dei messaggi. Nell'esempio seguente viene illustrato l'utilizzo non corretto dei flussi durante la definizione del contratto dell'operazione.
//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);
'Incorrect:
'<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
Nell'esempio seguente viene illustrato l'utilizzo corretto dei flussi durante la definizione di un contratto di operazione.
[OperationContract]
public void UploadFile (UploadFileMessage message);
//code omitted
[MessageContract]
public class UploadFileMessage
{
[MessageHeader] public string fileName;
[MessageBodyMember] public Stream fileData;
}
<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
'Code Omitted
<MessageContract()>
Public Class UploadFileMessage
<MessageHeader()>
Public Property fileName As String
<MessageBodyMember()>
Public Property fileData As Stream
End Class
Per altre informazioni, vedere Dati di grandi dimensioni e streaming.
Utilizzo della classe Message
Per avere un controllo a livello di codice completo sui messaggi inviati o ricevuti, è possibile usare direttamente la Message classe , come illustrato nel codice di esempio seguente.
[OperationContract]
public void LogMessage(Message m);
<OperationContract()>
Sub LogMessage(m As Message)
Si tratta di uno scenario avanzato, descritto in dettaglio in Uso della classe messaggio.
Descrizione dei messaggi di errore
Oltre ai messaggi descritti dal valore restituito e dai parametri di output o riferimento, qualsiasi operazione non unidirezionale può restituire almeno due messaggi possibili: il messaggio di risposta normale e un messaggio di errore. Si consideri il contratto dell'operazione seguente.
[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)
Questa operazione può restituire un normale messaggio contenente un float numero o un messaggio di errore contenente un codice di errore e una descrizione. A tale scopo, è possibile generare un'eccezione FaultException nell'implementazione del servizio.
È possibile specificare altri possibili messaggi di errore usando l'attributo FaultContractAttribute . Gli errori aggiuntivi devono essere serializzabili usando DataContractSerializer, come illustrato nel codice di esempio seguente.
[OperationContract]
[FaultContract(typeof(ItineraryNotAvailableFault))]
float GetAirfare(string fromCity, string toCity, DateTime date);
//code omitted
[DataContract]
public class ItineraryNotAvailableFault
{
[DataMember]
public bool IsAlternativeDateAvailable;
[DataMember]
public DateTime alternativeSuggestedDate;
}
<OperationContract()>
<FaultContract(GetType(ItineraryNotAvailableFault))>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime) As Double
'Code Omitted
<DataContract()>
Public Class
<DataMember()>
Public Property IsAlternativeDateAvailable As Boolean
<DataMember()>
Public Property alternativeSuggestedDate As DateTime
End Class
Questi errori aggiuntivi possono essere generati lanciando un FaultException<TDetail> dell'appropriato tipo di contratto di dati. Per altre informazioni, vedere Gestione di eccezioni e errori.
Non è possibile usare la XmlSerializer classe per descrivere gli errori. XmlSerializerFormatAttribute non ha alcun effetto sui contratti di guasto.
Uso dei tipi derivati
È possibile usare un tipo di base in un'operazione o un contratto di messaggio e quindi usare un tipo derivato quando si richiama effettivamente l'operazione. In questo caso, è necessario usare l'attributo ServiceKnownTypeAttribute o un meccanismo alternativo per consentire l'uso di tipi derivati. Si consideri l'operazione seguente.
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
Si supponga che due tipi, Book e Magazine, derivino da LibraryItem. Per usare questi tipi nell'operazione IsLibraryItemAvailable , è possibile modificare l'operazione nel modo seguente:
[OperationContract]
[ServiceKnownType(typeof(Book))]
[ServiceKnownType(typeof(Magazine))]
public bool IsLibraryItemAvailable(LibraryItem item);
In alternativa, è possibile usare l'attributo quando l'impostazione KnownTypeAttribute predefinita DataContractSerializer è in uso, come illustrato nel codice di esempio seguente.
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
// code omitted
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
//code omitted
}
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
'Code Omitted
<DataContract()>
<KnownType(GetType(Book))>
<KnownType(GetType(Magazine))>
Public Class LibraryItem
'Code Omitted
End Class
È possibile usare l'attributo XmlIncludeAttribute quando si usa il XmlSerializer.
È possibile applicare l'attributo ServiceKnownTypeAttribute a un'operazione o all'intero servizio. Accetta un tipo o il nome del metodo da chiamare per ottenere un elenco di tipi noti, proprio come l'attributo KnownTypeAttribute . Per altre informazioni, vedere Tipi noti del contratto dati.
Specifica dell'utilizzo e dello stile
Quando si descrivono i servizi tramite WSDL (Web Services Description Language), i due stili di uso comune sono Document e remote procedure call (RPC). Nello stile Documento, l'intero corpo del messaggio viene descritto usando lo schema e WSDL descrive le varie parti del corpo del messaggio facendo riferimento agli elementi all'interno di tale schema. Nello stile RPC il linguaggio WSDL fa riferimento a un tipo di schema per ogni parte del messaggio anziché a un elemento. In alcuni casi, è necessario selezionare manualmente uno di questi stili. Puoi fare questo applicando l'attributo DataContractFormatAttribute e impostando la proprietà Style (quando DataContractSerializer è in uso), oppure impostando Style nell'attributo XmlSerializerFormatAttribute (quando si usa XmlSerializer).
Supporta inoltre XmlSerializer due forme di XML serializzato: Literal e Encoded.
Literal è il modulo più comunemente accettato ed è l'unico formato supportato DataContractSerializer .
Encoded è un modulo legacy descritto nella sezione 5 della specifica SOAP e non è consigliato per i nuovi servizi. Per passare alla Encoded modalità, impostare la Use proprietà sull'attributo XmlSerializerFormatAttribute su Encoded.
Nella maggior parte dei casi, non è consigliabile modificare le impostazioni predefinite per le Style proprietà e Use .
Controllo del processo di serializzazione
È possibile eseguire diverse operazioni per personalizzare la modalità di serializzazione dei dati.
Modifica delle impostazioni di serializzazione del server
Quando l'impostazione predefinita DataContractSerializer è in uso, è possibile controllare alcuni aspetti del processo di serializzazione nel servizio applicando l'attributo ServiceBehaviorAttribute al servizio. In particolare, è possibile utilizzare la MaxItemsInObjectGraph proprietà per impostare la quota che limita il numero massimo di oggetti deserializzati DataContractSerializer . È possibile utilizzare la proprietà IgnoreExtensionDataObject per disattivare la funzionalità di versioning di andata e ritorno. Per altre informazioni sulle quote, vedere Considerazioni sulla sicurezza per i dati. Per ulteriori informazioni sul round-tripping, vedere Forward-Compatible Contratti di dati.
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public class MyDataService:IDataService
{
public DataPoint[] GetData()
{
// Implementation omitted
}
}
<ServiceBehavior(MaxItemsInObjectGraph:=100000)>
Public Class MyDataService Implements IDataService
Function GetData() As DataPoint()
‘ Implementation omitted
End Function
End Interface
Comportamenti di serializzazione
In WCF sono disponibili due comportamenti, DataContractSerializerOperationBehavior e XmlSerializerOperationBehavior che vengono collegati automaticamente a seconda del serializzatore in uso per una determinata operazione. Poiché questi comportamenti vengono applicati automaticamente, in genere non è necessario conoscerli.
Tuttavia, l'oggetto DataContractSerializerOperationBehavior ha le proprietà MaxItemsInObjectGraph, IgnoreExtensionDataObject e DataContractSurrogate che è possibile usare per personalizzare il processo di serializzazione. Le prime due proprietà hanno lo stesso significato descritto nella sezione precedente. È possibile usare la DataContractSurrogate proprietà per abilitare i surrogati del contratto dati, un meccanismo potente per personalizzare ed estendere il processo di serializzazione. Per altre informazioni, vedere Sostituti del contratto di dati.
È possibile usare DataContractSerializerOperationBehavior per personalizzare la serializzazione sia client che server. Nell'esempio seguente viene illustrato come aumentare la MaxItemsInObjectGraph quota nel client.
ChannelFactory<IDataService> factory = new ChannelFactory<IDataService>(binding, address);
foreach (OperationDescription op in factory.Endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior =
op.Behaviors.Find<DataContractSerializerOperationBehavior>()
as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = 100000;
}
}
IDataService client = factory.CreateChannel();
Dim factory As ChannelFactory(Of IDataService) = New ChannelFactory(Of IDataService)(binding, address)
For Each op As OperationDescription In factory.Endpoint.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Dim client As IDataService = factory.CreateChannel
Ecco il codice equivalente sul servizio, nel caso autogestito.
ServiceHost serviceHost = new ServiceHost(typeof(IDataService))
foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
{
foreach (OperationDescription op in ep.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior =
op.Behaviors.Find<DataContractSerializerOperationBehavior>()
as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = 100000;
}
}
}
serviceHost.Open();
Dim serviceHost As ServiceHost = New ServiceHost(GetType(IDataService))
For Each ep As ServiceEndpoint In serviceHost.Description.Endpoints
For Each op As OperationDescription In ep.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Next
serviceHost.Open()
Nel caso ospitato sul Web, è necessario creare una nuova ServiceHost classe derivata e usare una factory host del servizio per collegarla.
Controllo delle impostazioni di serializzazione nella configurazione
MaxItemsInObjectGraph e IgnoreExtensionDataObject possono essere controllati tramite la configurazione usando il comportamento del servizio o l'endpoint dataContractSerializer, come illustrato nell'esempio seguente.
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="LargeQuotaBehavior">
<dataContractSerializer
maxItemsInObjectGraph="100000" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://example.com/myservice"
behaviorConfiguration="LargeQuotaBehavior"
binding="basicHttpBinding" bindingConfiguration=""
contract="IDataService"
name="" />
</client>
</system.serviceModel>
</configuration>
Serializzazione dei tipi condivisi, conservazione dell'oggetto grafico e serializzatori personalizzati
Serializza DataContractSerializer utilizzando nomi di contratto dati e non nomi di tipi .NET. Ciò è coerente con i set di principi di architettura orientata ai servizi e consente un elevato grado di flessibilità. I tipi .NET possono cambiare senza influire sul contratto di rete. In rari casi, potresti voler serializzare i nomi effettivi dei tipi .NET, stabilendo così un collegamento stretto tra il client e il server, simile alla tecnologia di remoting del .NET Framework. Questa non è una procedura consigliata, ad eccezione dei rari casi che si verificano in genere durante la migrazione a WCF dalla comunicazione remota di .NET Framework. In questo caso, è necessario usare la NetDataContractSerializer classe anziché la DataContractSerializer classe .
In genere, DataContractSerializer serializza i grafi di oggetti come alberi di oggetti. Ovvero, se lo stesso oggetto viene fatto riferimento più volte, viene serializzato più volte. Si consideri, ad esempio, un'istanza PurchaseOrder con due campi di tipo Address denominato billTo e shipTo. Se entrambi i campi sono impostati sulla stessa istanza di Address, sono presenti due istanze Address identiche dopo la serializzazione e la deserializzazione. Questa operazione viene eseguita perché non esiste un modo interoperabile standard per rappresentare gli oggetti grafici in XML (ad eccezione dello standard con codifica SOAP legacy disponibile in XmlSerializer, come descritto nella sezione precedente su Style e Use). La serializzazione di grafici di oggetti come alberi presenta alcuni svantaggi, ad esempio grafici con riferimenti circolari non possono essere serializzati. In alcuni casi, è necessario passare alla serializzazione del grafico a oggetti true, anche se non è interoperabile. A tale scopo, è possibile usare l'oggetto DataContractSerializer costruito con il preserveObjectReferences parametro impostato su true.
In alcuni casi, i serializzatori predefiniti non sono sufficienti per lo scenario in uso. Nella maggior parte dei casi, è comunque possibile usare l'astrazione XmlObjectSerializer da cui derivano sia l'oggetto DataContractSerializer che il NetDataContractSerializer .
I tre casi precedenti (conservazione dei tipi .NET, conservazione del grafico degli oggetti e serializzazione completamente personalizzata basata su XmlObjectSerializer) richiedono che un serializzatore personalizzato sia collegato. A tale scopo, eseguire la procedura seguente:
Scrivere un comportamento personalizzato partendo da DataContractSerializerOperationBehavior.
Esegui l'override dei due metodi
CreateSerializerper restituire il tuo serializzatore (il NetDataContractSerializer, il DataContractSerializer conpreserveObjectReferencesimpostato sutrue, o il tuo personalizzato XmlObjectSerializer).Prima di aprire l'host del servizio o creare un canale client, rimuovere il comportamento esistente DataContractSerializerOperationBehavior e collegare la classe derivata personalizzata creata nei passaggi precedenti.
Per altre informazioni sui concetti di serializzazione avanzati, vedere Serializzazione e deserializzazione.