niedziela, 27 października 2013

[WCF] ServiceContract, DataContract, MessageContract

Kontrakty w WCF można porównać do kontraktów w życiu codziennym. Podpisując jakiś kontrakt obie strony potwierdzają, że są świadome pewnych warunków. W WCF kontrakt definiuje między innymi wystawione serwisy, adresy, rodzaje wiadomości, jakie te serwisy odbierają i zwracają. WCF wykorzystuje  WSDL (Web Services Description Language) oraz XSD do dostarczenie metadanych na temat wystawionych serwisów. Ważne jest, aby kontrakty były niezależne od technologii, dlatego definiuje się je w neutralnym formacie bazującym na XML. Dzięki temu WebSerwisy wystawione na przykład w technologiach Javy mogą być wykorzystywane przez proxy WCF i aplikację napisaną w .NET i na odwrót.

ServiceContract

Definiuje funkcjonalność wystawioną przez serwis WCF. Dobrą praktyką jest oznaczanie tym atrybutem interfejsu .NET tak, aby zapewnić pewną warstwę abstrakcji nad konkretnymi implementacjami. Przykładowo:

[ServiceContract]
public interface IEvaluationService
{
    [OperationContract(IsOneWay = true)]
    void SubmitEvaluation(Evaluation eval);
    [OperationContract]
    List<Evaluation> GetEvaluations();
}

Property IsOneWay oznacza, że dana operacja nie zwraca wyniku. Konkretną implementację interfejsu można konfigurować poprzez atrybut ServiceBehavior.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
    ConcurrencyMode = ConcurrencyMode.Single)]
public class EvaluationService : IEvaluationService
{
    private List<Evaluation> _evals = new List<Evaluation>();

    public void SubmitEvaluation(Evaluation eval)
    {
        _evals.Add(eval);
    }

    public List<Evaluation> GetEvaluations()
    {
        return _evals;
    }
}

InstanceContextMode:  Single - Singleton, PerCall - zawsze nowa instancje, Session - każdy klient dostaje unikalną instancje w obrębie sesji.
ConcurrencyMode - Single - tylko jeden wątek ma dostęp do obiektu, Multiple - dostępny dla wielu wątków, Reentrant - nowe operacje mogą być wykonywane tylko podczas wywoływania innego serwisu.

DataContract

Definiuje typy, które mają być serializowane i przesyłane dalej przez WCF. Domyślny serializator to DataContractSerializer, który sprawdza, które obiekty są oznaczone tym atrybutem. Serializowane będą te propercje lub pola, które są oznaczone atrybutami DataMember.

[DataContract]
public class Evaluation
{
    [DataMember(IsRequired = true)]
    public string Submitted { get; set; }
    [DataMember(Order = 2)]
    public DateTime TimeSent { get; set; }
    [DataMember]
    public string Comments { get; set; }
}

Jeżeli w odebranej wiadomości nie będzie wartości, którą możnaby zdeserializować na property oznaczone z opcją IsRequired, aplikacja zwróci błąd. Poprzez opcję Order możemy ustawiać kolejność elementów w XML-u.

Problem może pojawić się w sytuacji, gdy chcemy wykorzystać mechanizm polimorfizmu. Jeżeli w sygnaturach metod mamy klasy bazowe, natomiast chcemy zwrócić klasę po nich dziedziczącą musimy użyć atrybutu KnownType. Znane typy dziedziczące po klasie bazowej zostaną dodane do kontraktu..

[DataContract]
[KnownType(typeof(Car))]
public abstract class Vehicle
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Car : Vehicle
{
    [DataMember]
    public int Year { get; set; }
}

MessageContract

Pozwala na zdefiniowanie, które pola mają znajdować się w nagłówku SOAP, a które w body. Typ oznaczony typ atrybutem przekazujemy do metody oznaczonej atrybutem OperationContract. 

[DataContract]
public class Evaluation
{
    [DataMember(IsRequired = true)]
    public string Submitted { get; set; }
    [DataMember(Order = 2)]
    public DateTime TimeSent { get; set; }
    [DataMember]
    public string Comments { get; set; }
}

[DataContract]
public class CustomHeader
{
   [DataMember]
   public string Username { get; set; }
}

[MessageContract]
public class EvaluationRequest
{
    [MessageHeader]
    public CustomHeader EvaluationHeader { get; set; }

    [MessageBodyMember]
    public Evaluation EvaluationBody { get; set; }
}

Dzięki temu wiadomość SOAP przyjmuje poniższą postać:

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IEvaluationService/SubmitEvaluation</a:Action>
    <h:EvaluationHeader xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:h="http://tempuri.org/">
      <Username xmlns="http://schemas.datacontract.org/2004/07/EvaluationServiceLibrary">Joe</Username>
    </h:EvaluationHeader>
  </s:Header>
  <s:Body>
    <EvaluationRequest xmlns="http://tempuri.org/">
      <EvaluationBody xmlns:d4p1="http://schemas.datacontract.org/2004/07/EvaluationServiceLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:Comments>Git</d4p1:Comments>
        <d4p1:Submitted>Yes</d4p1:Submitted>
        <d4p1:TimeSent>2013-10-27T12:46:00</d4p1:TimeSent>
      </EvaluationBody>
    </EvaluationRequest>
  </s:Body>
</s:Envelope>

Brak komentarzy:

Prześlij komentarz