Spécification du transfert de données dans des contrats de service

Windows Communication Foundation (WCF) peut être considéré comme une infrastructure de messagerie. Les opérations de service peuvent recevoir des messages, les traiter et leur envoyer des messages. Les messages sont décrits à l'aide de contrats d'opérations. Par exemple, considérons le contrat suivant.

[ServiceContract]
public interface IAirfareQuoteService
{
    [OperationContract]
    float GetAirfare(string fromCity, string toCity);
}

Ici, l'opération GetAirfare accepte un message avec des informations à propos de fromCity et toCity, puis retourne un message qui contient un nombre.

Cette rubrique explique les différentes manières par lesquelles un contrat d'opération peut décrire des messages.

Description de messages à l'aide de paramètres

La méthode la plus simple pour décrire un message consiste à utiliser une liste de paramètres et la valeur de retour. Dans l'exemple précédent, les paramètres de chaîne fromCity et toCity ont été utilisés pour décrire le message de demande et la valeur de retour float a été utilisée pour décrire le message de réponse. Si la valeur de retour seule n'est pas suffisante pour décrire un message de réponse, les paramètres de sortie peuvent être utilisés. Par exemple, l'opération suivante a fromCity et toCity dans son message de demande, et un nombre et une monnaie dans son message de réponse :

[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);

En outre, vous pouvez utiliser des paramètres de référence pour intégrer un paramètre à la fois au message de demande et au message de réponse. Les paramètres doivent être de types pouvant être sérialisés (convertis en XML). Par défaut, WCF utilise un composant appelé classe DataContractSerializer pour exécuter cette conversion. La plupart des types primitifs (comme int, string, float et DateTime) est pris en charge. Les types définis par l'utilisateur doivent normalement avoir un contrat de données. Pour plus d'informations, consultez Utilisation de contrats de données.

public interface IAirfareQuoteService
{
    [OperationContract]
    float GetAirfare(Itinerary itinerary, DateTime date);
    }
    [DataContract]
    public class Itinerary
    {
        [DataMember]
        public string fromCity;
        [DataMember]
        public string toCity;
}

Parfois, le DataContractSerializer ne convient pas pour sérialiser vos types. WCF prend en charge un autre moteur de sérialisation, le XmlSerializer, que vous pouvez également utiliser pour sérialiser des paramètres. Le XmlSerializer vous permet de davantage contrôler le XML résultant à l'aide d'attributs tels que XmlAttributeAttribute. Pour utiliser le XmlSerializer pour une opération particulière ou pour le service entier, appliquez l'attribut XmlSerializerFormatAttribute à une opération ou à un service. Par exemple :

[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;
}

Pour plus d'informations, consultez Utilisation de la classe XmlSerializer. Souvenez-vous que le basculement manuel vers le XmlSerializer, comme illustré ici, n'est pas recommandé, à moins que vous n'ayez des raisons spécifiques telles que celles détaillées dans cette rubrique.

Pour isoler des noms de paramètres .NET des noms de contrats, vous pouvez utiliser l'attribut MessageParameterAttribute et utiliser la propriété Name pour définir le nom de contrat. Par exemple, le contrat d'opération suivant est équivalent au premier exemple dans cette rubrique.

[OperationContract]
public float GetAirfare(
    [MessageParameter(Name="fromCity")] string originCity,
    [MessageParameter(Name="toCity")] string destinationCity);

Description de messages vides

Un message de demande vide peut être décrit en n'ayant ni entrée ni paramètre de référence. Par exemple :

[OperationContract]

public int GetCurrentTemperature();

Un message de réponse vide peut être décrit en ayant un type de retour void et aucune sortie ni paramètre de référence. Par exemple :

[OperationContract]
public void SetTemperature(int temperature);

Ceci est différent d'une opération unidirectionnelle telle que :

[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);

L'opération SetTemperatureStatus retourne un message vide. Elle peut retourner une erreur à la place, en cas de problème au niveau du traitement du message d'entrée. L'opération SetLightbulbStatus ne retourne rien. Il n'existe aucun moyen de communiquer une condition d'erreur à partir de cette opération.

Description de messages à l'aide de contrats de message

Vous souhaiterez peut-être utiliser un type unique pour représenter le message entier. Bien qu'il soit possible d'utiliser un contrat de données dans ce but, la méthode recommandée consiste à utiliser un contrat de message ; cela évite des niveaux d'enveloppement inutiles dans le XML résultant. En outre, les contrats de message vous permettent de mieux contrôler les messages résultants. Par exemple, vous pouvez décider des informations qui doivent être dans le corps du message et de celles qui doivent être dans les en-têtes de message. L'exemple suivant illustre l'utilisation des contrats de message.

[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;
}

Pour plus d'informations, consultez Utilisation de contrats de message.

Dans l'exemple précédent, la classe DataContractSerializer est encore utilisée par défaut. La classe XmlSerializer peut également être utilisée avec des contrats de message. Pour cela, appliquez l'attribut XmlSerializerFormatAttribute à l'opération ou au contrat et utilisez des types compatibles avec la classe XmlSerializer dans les en-têtes de message et les membres de corps.

Description de messages à l'aide de flux

Une autre méthode pour décrire des messages dans des opérations consiste à utiliser la classe Stream ou une de ses classes dérivées dans un contrat d'opération ou en tant que membre de corps de contrat de message (il doit s'agir du seul membre dans ce cas). Pour les messages entrants, le type doit être Stream ; vous ne pouvez pas utiliser de classes dérivées.

Au lieu d'appeler le sérialiseur, WCF récupère les données d'un flux et les met directement dans un message sortant, ou récupère les données d'un message entrant et les met directement dans un flux. L'exemple suivant illustre l'utilisation des flux.

[OperationContract]
public Stream DownloadFile(string fileName);

Vous ne pouvez pas combiner des données Stream et des données de non-flux dans un même corps de message. Utilisez un contrat de message pour mettre les données supplémentaires dans les en-têtes de message. L'exemple suivant illustre l'utilisation incorrecte de flux lors de la définition du contrat d'opération.

//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);

L'exemple suivant illustre l'utilisation correcte de flux lors de la définition d'un contrat d'opération.

[OperationContract]
public void UploadFile (UploadFileMessage message);
//code omitted
[MessageContract]
public class UploadFileMessage
{
    [MessageHeader] public string fileName;
    [MessageBodyMember] public Stream fileData;
}

Pour plus d'informations, consultez Données volumineuses et diffusion en continu.

Utilisation de la classe Message

Pour avoir un contrôle complet par programmation des messages envoyés ou reçus, vous pouvez utiliser la classe Message directement, comme illustré dans l'exemple de code suivant.

[OperationContract]
public void LogMessage(Message m);

Il s'agit d'un scénario avancé, décrit en détail dans Utilisation de la classe Message.

Description des messages d'erreur

En plus des messages décrits par la valeur de retour et les paramètres de sortie ou de référence, toute opération qui n'est pas unidirectionnelle peut retourner au moins deux messages possibles : son message de réponse normal et un message d'erreur. Considérez le contrat d'opération suivant.

[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);

Cette opération peut retourner un message normal qui contient un nombre float ou un message d'erreur qui contient un code d'erreur et une description. Vous pouvez accomplir cela en levant une FaultException dans votre implémentation de service.

Vous pouvez spécifier des messages d'erreur possibles supplémentaires en utilisant l'attribut FaultContractAttribute. Les erreurs supplémentaires doivent être sérialisables à l'aide du DataContractSerializer, comme illustré dans l'exemple de code suivant.

[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;
}

Ces erreurs supplémentaires peuvent être générées en levant une FaultException du type de contrat de données approprié. Pour plus d'informations, consultez Gestion des exceptions et des erreurs.

Vous ne pouvez pas utiliser la classe XmlSerializer pour décrire des erreurs. Le XmlSerializerFormatAttribute n'a aucun effet sur les contrats d'erreur.

Utilisation de types dérivés

Vous pouvez utiliser un type de base dans une opération ou un contrat de message, puis utiliser un type dérivé lors de l'appel réel à l'opération. Dans ce cas, vous devez utiliser l'attribut ServiceKnownTypeAttribute ou un autre mécanisme pour autoriser l'utilisation de types dérivés. Considérez l'opération suivante.

[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);

Supposez que deux types, Book et Magazine, dérivent de LibraryItem. Pour utiliser ces types dans l'opération IsLibraryItemAvailable, vous pouvez modifier l'opération comme suit :

[OperationContract]

[ServiceKnownType(typeof(Book))]

[ServiceKnownType(typeof(Magazine))]

public bool IsLibraryItemAvailable(LibraryItem item);

Vous pouvez également utiliser l'attribut KnownTypeAttribute lorsque le DataContractSerializer par défaut est en cours d'utilisation, comme illustré dans l'exemple de code suivant.

[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);

// code omitted 

[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
    //code omitted
}

Vous pouvez utiliser l'attribut XmlIncludeAttribute lors de l'utilisation du XmlSerializer.

Vous pouvez appliquer l'attribut ServiceKnownTypeAttribute à une opération ou au service entier. Il accepte un type ou le nom de la méthode à appeler pour obtenir une liste des types connus, tout comme l'attribut KnownTypeAttribute. Pour plus d'informations, consultez Types connus de contrats de données.

Spécification de l'utilisation et du style

Lors de la description de services à l'aide du langage WSDL (Web Services Description Language), les deux styles couramment utilisés sont Document et appel de procédure distante (RPC). Dans le style Document, le corps du message entier est décrit à l'aide du schéma et le WSDL décrit les différentes parties du corps de message en faisant référence à des éléments dans ce schéma. Dans le style RPC, le WSDL fait référence à un type de schéma pour chaque partie de message plutôt qu'à un élément. Dans certains cas, vous devez sélectionner manuellement l'un de ces styles. Pour cela, vous pouvez appliquer l'attribut DataContractFormatAttribute et définir la propriété Style (lorsque le DataContractSerializer est en cours d'utilisation), ou définir Style sur l'attribut XmlSerializerFormatAttribute (lors de l'utilisation du XmlSerializer).

En outre, le XmlSerializer prend en charge deux formes de XML sérialisé : Literal et Encoded. Literal est la forme la plus couramment acceptée et la seule prise en charge par le DataContractSerializer. Encoded est une forme héritée décrite à la section 5 de la spécification SOAP qui n'est pas recommandée pour les nouveaux services. Pour basculer en mode Encoded, affectez à la propriété Use sur l'attribut XmlSerializerFormatAttribute la valeur Encoded.

Dans la plupart des cas, vous ne devez pas modifier les paramètres par défaut des propriétés Style et Use.

Contrôle du processus de sérialisation

Vous pouvez effectuer plusieurs actions pour personnaliser la manière dont les données sont sérialisées.

Modification des paramètres de sérialisation du serveur

Lorsque le DataContractSerializer par défaut est en cours d'utilisation, vous pouvez contrôler certains aspects du processus de sérialisation sur le service en appliquant l'attribut ServiceBehaviorAttribute au service. Spécifiquement, vous pouvez utiliser la propriété MaxItemsInObjectGraph pour définir le quota qui limite le nombre maximal d'objets que le DataContractSerializer désérialise. Vous pouvez utiliser la propriété IgnoreExtensionDataObject pour désactiver la fonctionnalité de suivi des versions aller-retour. Pour plus d'informations sur le sujet suivant les quotas, consultez Considérations sur la sécurité des données. Pour plus d'informations sur le sujet suivant l'aller-retour, consultez Contrats de données à compatibilité ascendante.

[ServiceContract]
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public interface IDataService
{
    [OperationContract] DataPoint[] GetData();
}

Comportements de sérialisation

Deux comportements sont disponibles dans WCF, le DataContractSerializerOperationBehavior et le XmlSerializerOperationBehavior qui sont connectés automatiquement en fonction du sérialiseur utilisé pour une opération particulière. Ces comportements étant appliqués automatiquement, il n'est normalement pas nécessaire de s'en soucier.

Toutefois, le DataContractSerializerOperationBehavior a les propriétés MaxItemsInObjectGraph, IgnoreExtensionDataObject et DataContractSurrogate que vous pouvez utiliser pour personnaliser le processus de sérialisation. Les deux premières propriétés ont la même signification que dans la section précédente. Vous pouvez utiliser la propriété DataContractSurrogate pour activer des substituts de contrat de données, qui sont un mécanisme puissant pour personnaliser et étendre le processus de sérialisation. Pour plus d'informations, consultez Substituts de contrats de données.

Vous pouvez utiliser le DataContractSerializerOperationBehavior pour personnaliser à la fois la sérialisation de client et de serveur. L'exemple suivant indique comment augmenter le quota MaxItemsInObjectGraph sur le 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();

Voici le code équivalent sur le service, dans le cas d'un auto-hébergement.

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();

Dans le cas d'un hébergement sur le Web, vous devez créer une classe dérivée ServiceHost et utiliser une fabrication hôte de service pour le brancher.

Contrôle des paramètres de sérialisation dans la configuration

Le MaxItemsInObjectGraph et le IgnoreExtensionDataObject peuvent être contrôlés par le biais de la configuration en utilisant le comportement de service ou le point de terminaison dataContractSerializer, comme illustré dans l'exemple suivant.

<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>

Sérialisation de type partagé, conservation de graphique d'objet et sérialiseurs personnalisés

Le DataContractSerializer sérialise à l'aide des noms de contrats de données, et non des noms de types .NET. Ceci est cohérent avec les doctrines d'architecture orientée services et procure un niveau élevé de flexibilité : les types .NET peuvent changer sans affecter le contrat de câble. Dans de rares cas, vous souhaiterez peut-être sérialiser des noms de types .NET réels, introduisant ainsi un couplage étroit entre le client et le serveur, semblable à la technologie de .NET Framework Remoting. Cette pratique est déconseillée, sauf dans les cas rares qui se produisent habituellement lors de la migration vers WCF à partir de .NET Framework Remoting. Dans ce cas, vous devez utiliser la classe NetDataContractSerializer au lieu de la classe DataContractSerializer.

Le DataContractSerializer sérialise normalement les graphiques d'objets en tant qu'arborescences d'objets. Autrement dit, s'il est fait référence plusieurs fois au même objet, il est sérialisé plusieurs fois. Par exemple, considérez une instance PurchaseOrder qui a deux champs de type Adresse nommés billTo et shipTo. Si les deux champs ont pour valeur la même instance Adresse, il existe deux instances Adresse identiques après la sérialisation et la désérialisation. Cela est dû au fait qu'il n'existe aucune méthode interopérable standard pour représenter des graphiques d'objets en XML (hormis la norme encodée SOAP héritée disponible sur le XmlSerializer, comme décrit dans la section précédente sur Style et Use). La sérialisation de graphiques d'objets en tant qu'arborescences a certains inconvénients ; par exemple, les graphiques avec des références circulaires ne peuvent pas être sérialisés. Parfois, il est nécessaire de basculer vers la sérialisation de graphiques d'objets vraie, bien que cela ne soit pas interopérable. Vous devez pour cela utiliser le DataContractSerializer construit avec le paramètre preserveObjectReferences défini à true.

Parfois, les sérialiseurs intégrés ne sont pas suffisants pour votre scénario. Dans la plupart des cas, vous pouvez encore utiliser l'abstraction XmlObjectSerializer à partir de laquelle le DataContractSerializer et le NetDataContractSerializer dérivent.

Les trois cas précédents (conservation de type .NET, conservation des graphiques d'objets et sérialisation XmlObjectSerializer complètement personnalisée) requièrent tous le branchement d'un sérialiseur personnalisé. Pour cela, exécutez les étapes suivantes :

  1. Écrivez votre propre comportement dérivant du DataContractSerializerOperationBehavior.

  2. Substituez les deux méthodes CreateSerializer pour retourner votre propre sérialiseur (le NetDataContractSerializer, le DataContractSerializer avec preserveObjectReferences défini à true, ou votre propre XmlObjectSerializer personnalisé).

  3. Avant d'ouvrir l'hôte de service ou de créer un canal client, supprimez le comportement DataContractSerializerOperationBehavior existant et branchez la classe dérivée personnalisée que vous avez créée aux étapes précédentes.

Pour plus d'informations sur le sujet suivant les concepts de sérialisation avancée, consultez Sérialisation et désérialisation.

Voir aussi

Tâches

Comment : activer la diffusion en continu
Comment : créer un contrat de données de base destiné à une classe ou une structure

Concepts

Utilisation de la classe XmlSerializer