WCF: Address, Binding, Contract!

Door CodeCaster op maandag 19 september 2011 21:00 - Reacties (8)
Categorie: Tech, Views: 5.910

Het ABC van WCF (ik hou echt van TLA's) luidt: Address, Binding, Contract. Tezamen vormen deze drie onderdelen de abstracte kern van alle soorten services: waar is 'ie bereikbaar, hoe communiceer ik ermee en welke data mag ik opsturen en kan ik terug verwachten?

Het adres is de locatie waarop de service bereikbaar is. Geldige adressen kunnen bijvoorbeeld http://localhost/Foo/ en http://server.domain.tld:8080/Bar/ zijn, maar ook
net.tcp://server.domain.local:9999/Baz/ is bruikbaar. Je ziet dat het protocol in het laatste geval niet HTTP is maar NET.TCP, een protocol dat enkel gebruikt kan worden wanneer server en client beide in WCF zijn geschreven.

De binding die wordt gebruikt om een service aan te spreken valt dus in sommige gevallen al uit het adres te achterhalen. Wanneer je echter kijkt naar bindings als basicHttp en wsHttp, dan zie je al dat ze wellicht allebei over HTTP communiceren, maar dat de inhoud van het bericht niet per se overeen hoeft te komen.

De manier waarop consumenten berichten naar de service kunnen sturen wordt bepaald door de toegepaste bindings. Een service is niet gelimiteerd tot het gebruik van één binding: je kunt een service dus prima via basicHttp benaderbaar maken, zodat bezoekers vanaf het internet gewone SOAP-berichten kunnen sturen, terwijl er voor intern gebruik gebruik gemaakt wordt van de netTcpBinding, die meer opties qua beveiliging en datacompressie heeft.

Het is zelfs mogelijk zelf een binding te schrijven die bijvoorbeeld aanvragen uitleest uit csv-bestanden in een directory, en het antwoord hierop wegschrijft in een database: in je WCF-service merk je hier niets van, de code die voor een aanvraag wordt uitgevoerd wordt in alle gevallen gevoerd met dezelfde parameters, en het eventuele antwoordobject wordt door de binding weer "op de draad gezet". Het maakt een service dus niets uit hoe hij wordt aangesproken!
Contracten
Er zijn twee typen contracten: je hebt een servicecontract, dat de interface van je service beschrijft, en er zijn datacontracten, die aangeven met welke datatypes de methodes van de service werken.

In de vorige blog liet ik hier al een stukje van zien. De attributen ServiceContract en OperationContract vertellen dat de interfaces (of klassen) en methodes die je ermee decoreert openbaar gemaakt mogen worden als service en aanroep.

Uiteraard is niets zo saai als droge, niet toe te passen code, dus ik maak voor het verdere verloop van deze serie blogs gebruik van een casus om de voorbeelden te verrijken: we gaan een postcodeservice bouwen, waarbij je adressen en postcodes kunt opvragen. In eerste instantie is de service zeer eenvoudig, maar naarmate ik tijd heb om meer te bloggen zal 'ie worden uitgebreid met de diverse onderdelen die het .NET-framework en met name WCF te bieden heeft.

In eerste instantie is dit de interface IPostcodeService:

C#:
1
2
3
4
5
6
7
8
9
namespace PostcodeService
{
    [ServiceContract]
    public interface IPostcodeService
    {
        [OperationContract]
        Address GetTestAddress();
    }
}


Er zijn een aantal zaken interessant aan deze paar regels code. Zo staat de interface als service aangemerkt, niet de implementatie. Dit is een aanbevolen manier van werken (een zogeheten "best practice") die niet alleen voor WCF geldt maar voor programmeren in het algemeen. Het houdt het een en ander overzichtelijker.

Daarnaast is er één OperationContract: toegepast op de functie GetTestAddress, die blijkbaar een object van het type Address retourneert. Dit is de definitie van die klasse:


C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace PostcodeServiceModels
{
    [DataContract]
    public class Address
    {
        [DataMember]
        public String Street { get; set; }

        [DataMember]
        public String Number { get; set; }

        [DataMember]
        public String Addition { get; set; }

        [DataMember]
        public String Postcode { get; set; }

        [DataMember]
        public String City { get; set; }
    }
}



Het betreft hier een zogeheten 'complex type', een klasse die je zelf hebt gedefinieerd. De tegenhanger hiervan zijn 'simple types', zoals een string, int, of boolean. Deze kunnen zonder extra attribuut worden gebruikt als argument of returnwaarde van een functie die de service openbaar maakt.

Deze Address-klasse is gedecoreerd met attributen. Het DataContract geeft aan dat we deze klasse wel eens zouden willen versturen of ontvangen vanuit de service, en iedere property van de klasse is een DataMember, wat inhoudt dat de properties worden meegestuurd als een object van dit type over de lijn gaat. Iedere property die je wil kunnen uitlezen aan de clientkant, dient dus voorzien te zijn van een DataMember-attribuut.

De implementatie van de service ziet er als volgt uit:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace PostcodeService
{
    public class PostcodeService : IPostcodeService
    {
        public Address GetTestAddress()
        {
            Address address = new Address();

            address.Postcode = "9999 XX";
            address.Street = "Knolweg";
            address.Number = "1001";
            address.City = "Stitswerd";

            return address;
        }
    }
}

Serialisatie
Met deze drie bestanden, namelijk de interface en de implementatie van de service en de Address-klasse, ben je al klaar om de service te hosten. Door dit project te debuggen in Visual Studio start de WCF Test Client, waardoor je de aanroep kunt testen:

Testclient

Het is zoals je ziet dus niet heel moeilijk een service te maken die gebruik maakt van complexe datatypes, zonder daarvoor te hoeven weten wat er op de achtergrond gebeurt. Ook niet-WCF-clients hebben hier geen problemen mee, zolang je maar de juiste binding gebruikt. De volgende php-client:

PHP:
1
2
3
4
$client = new SoapClient("http://localhost:8732/PostcodeService/?wsdl");

$address = $client->GetTestAddress();
var_dump(get_object_vars($address));


voert deze data uit:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
array(1) {
  ["GetTestAddressResult"]=>
  object(stdClass)#3 (5) {
    ["Addition"]=>
    NULL
    ["City"]=>
    string(9) "Stitswerd"
    ["Number"]=>
    string(4) "1001"
    ["Postcode"]=>
    string(7) "9999 XX"
    ["Street"]=>
    string(7) "Knolweg"
  }
}



Hieraan is te zien dat dankzij de DataMember-attributen op de properties in de Address-klasse in C# worden ze geserialiseerd in een soap-object van eveneens de klasse Address. Dit is te zien aan de XSD die het type Address beschrijft:

XML:
1
2
3
4
5
6
7
8
9
10
11
12
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/PostcodeServiceModels" elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/PostcodeServiceModels">
    <xs:complexType name="Address">
        <xs:sequence>
            <xs:element minOccurs="0" name="Addition" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="City" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="Number" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="Postcode" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="Street" nillable="true" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
    <xs:element name="Address" nillable="true" type="tns:Address"/>
</xs:schema>



De volgorde is standaard alfabetisch, maar dit gedrag is gemakkelijk aan te passen. De diverse attributen die je voor WCF toepast hebben tal van opties, waar dit er een van is.
En nu?
Er is nu dus een service, die een testadres terugstuurt. Je kunt 'm trouwens hier downloaden, feel free to comment. Veel spannends is er nog niet aan, maar dat komt hopelijk in een volgend blog wel goed. :)


http://codecaster.nl/got/rmb/star1.pnghttp://codecaster.nl/got/rmb/star2.pnghttp://codecaster.nl/got/rmb/star3.pnghttp://codecaster.nl/got/rmb/star4.pnghttp://codecaster.nl/got/rmb/star5.pnghttp://codecaster.nl/got/rmb/stats.gif

Volgende: First world problems 11-'11 First world problems
Volgende: Windows Communication Foundation 09-'11 Windows Communication Foundation

Reacties


Door Tweakers user mmjjb, maandag 19 september 2011 21:25

Dank, zeer informatief artikel..

Ben zelf een leek op het gebied van Soap, zie zeker uit na een vervolg :)

Door Tweakers user ThaStealth, maandag 19 september 2011 21:40

Interessant artikel, ik zou er nog 2 dingen willen aan toevoegen, dit zijn namelijk dingen waar ik vrij snel tegenaanliep:

- Wat als een service een type van een basis object kan terugsturen (een overerving dus)

Stel je voor de volgende interface

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[DataContract]
public class Lada : Auto
{
    public override int Prijs
    {
        get { return 200; }
        set { }
    }

    public override int Deuren
    {
        get { return 4; }
        set { }
    }
}

[DataContract]
public class Ferrari : Auto
{
    public override int Prijs
    {
        get { return 200000; }
        set { }
    }


    public override int Deuren
    {
        get { return 2; }
        set { }
    }
}

[DataContract]
public abstract class Auto
{
    [DataMember]
    public virtual int Prijs { get; set; }

    [DataMember]
    public virtual int Deuren { get; set; }
}

[ServiceContract]
public interface AutoDealer
{
    [OperationContract]
    Auto KoopNieuweAuto(int bedrag);
}



Wanneer je deze code probeert te runnen en een Ferrari of Lada type teruggeeft zal de Client kant een fout geven (de client kant weet niet dat het een Ferrari of Lada type kan terug krijgen (laat staan hoe Ferrari of Lada opgebouwd is).

Om dit aan de client te vertellen kun je gebruik maken van KnownType

De implementatie van Auto ziet er dan als volgt uit:


C#:
1
2
3
4
5
6
7
8
9
10
11
[DataContract]
[KnownType(typeof(Ferrari))]
[KnownType(typeof(Lada))]
public abstract class Auto
{
    [DataMember]
    public virtual int Prijs { get; set; }

    [DataMember]
    public virtual int Deuren { get; set; }
}



Wanneer een proxy gegenereerd word van de service zullen deze 2 types ook bekend zijn

- Enums terugsturen
Bij enums moet je in sommige gevallen aangeven dat elk element een onderdeel is van de de enum. Dit doe je op de volgende manier:

C#:
1
2
3
4
5
6
7
8
9
10
[DataContract]
public enum OnderhoudsStatus
{
    [EnumMember]
    Nieuw= 10,
    [EnumMember]
    Gebruikt = 20,
    [EnumMember]
    Sloop= 30,
}



Volgens mij is het niet verplicht (alhoewel ik het wel altijd toepas wanneer ik een enum terug wil sturen). Volgens mij is het alleen verplicht wanneer er een numerieke waarde aan de enum waardes toegekend is die niet opvolgend is (zoals in het voorbeeld)

edit:
Even wat aan de uitlijning van de code gedaan

[Reactie gewijzigd op maandag 19 september 2011 21:45]


Door Tweakers user CodeCaster, maandag 19 september 2011 22:38

Mooie aanvulling, bedankt. :) De EnumMember is wel verplicht.

Ik ben overigens nog nooit een situatie tegengekomen waarbij ik KnownType kon gebruiken. Ik maak dan liever een klasse Auto voor de service, die je kunt casten vanuit de andere klasse Auto waarbij de properties gemapt worden.

De klassen die services gebruiken zijn bij mij niet meer dan een adapter, zodat je de businesslogica gescheiden kan houden. Zaken als overerving en methodes in een klasse die met een DataContract-attribuut is gedecoreerd vind ik daarom ook twijfelachtig.

[Reactie gewijzigd op maandag 19 september 2011 23:53]




Door Tweakers user Jogai, woensdag 21 september 2011 11:38

Informatief stuk. Leuk ook dat je een andere taal laat zien. Heb je ook een javascript/jquery voorbeeld? Dit is namelijk voor een .Net web-developer ook een belangrijke, plus dat iedere web-developer hiervoor de mogelijkheden heeft.

Door Tweakers user CodeCaster, donderdag 22 september 2011 19:08

Helaas, ik doe niks met javascript, dus ik kan daar geen nette code voor produceren (ik wil niet dat anderen via Google op deze blog komen en een 'bad practice' aangeleerd krijgen). :)

[Reactie gewijzigd op donderdag 22 september 2011 19:15]


Door Tweakers user WebHawk, donderdag 1 december 2011 09:24

CodeCaster, jij gore baas!

Als er iemand op Tweakers.net de Knolweg in Stitswerd moest kennen moest jij het wel zijn. Ik gebruik het al jaren als bogus-adress om vooral accounts met adresverificatie te kunnen aanmaken.

[Reactie gewijzigd op donderdag 1 december 2011 12:54]


Reageren is niet meer mogelijk