SOFTWARE DEVELOPMENT NETWORK
MAGAZINE
IN DIT NUMMER O.A.:
MP3 Tags in Delphi 2009 < Peer to Peer Applicaties met .NET Framework 3.5 < Zeven Windows Communication Foundation Best Practices < Portaltechnologie: SharePoint of DotNetNuke? < The Best of Both Worlds: Modernizing Core Sytems <
Nummer 99 November 2008 SDN Magazine verschijnt elk kwartaal en is een uitgave van Software Development Network
99
www.sdn.nl
Advertentie Giraffe
Colofon Uitgave: Software Development Network Postbus 506 7100 AM Winterswijk
[email protected] www.sdn.nl tel. 0543 518 058 fax 0543 515 399 Zeventiende jaargang No. 99 • november 2008
Bestuur van SDN: Remi Caron, voorzitter Rob Suurland, penningmeester Joop Pecht, secretaris Mark Vroom, vice-voorzitter
Redactie: Rob Willemsen (
[email protected])
Aan dit magazine werd meegewerkt door: Maurice de Beijer, Mark Blomsma, Rolf Craenen, Marcel van Kalken, Stefan Kamphuis, Marcel Meijer, Johan Parent, Joop Pecht, Maarten van Stam, Bob Swart, Louis vd Tol, Robertjan Tuit, Marianne van Wanrooij, Rob Willemsen en natuurlijk alle auteurs!
Listings: Zie de website www.sdn.nl voor eventuele source files uit deze uitgave.
Vormgeving en opmaak: Reclamebureau Bij Dageraad, Winterswijk www.bijdageraad.nl ©2008 Alle rechten voorbehouden. Niets uit deze uitgave mag worden overgenomen op welke wijze dan ook zonder voorafgaande schriftelijke toestemming van SDN. Tenzij anders vermeld zijn artikelen op persoonlijke titel geschreven en verwoorden zij dus niet noodzakelijkerwijs de mening van het bestuur en/of de redactie. Alle in dit magazine genoemde handelsmerken zijn het eigendom van hun respectievelijke eigenaren.
Adverteerders
Giraffe 2 Bergler Nederland b.v. 10 Logica 18 4DotNet b.v. 22 Microsoft 36-37 Avanade 41 Qurius AS 45 Aladdin 49 SIRA Holding BV 59 Infodis 66 iAnywhere Solutions/ a Sybase Company 66 Sybase iAnywhere / Elmo ICT Systems 71 Furore 72 Adverteren? Informatie over adverteren en de advertentietarieven kunt u vinden op www.sdn.nl onder de rubriek Magazine.
voorwoord Voor je ligt alweer het 3e nummer in de nieuwe vormgeving. Het lijkt erop alsof we inmiddels een behoorlijk evenwicht hebben gevonden, want dit keer is het vooral wat schaafwerk dat verricht wordt. We willen b.v. op de een of andere manier duidelijk maken welke pagina’s bij eenzelfde artikel horen. Daartoe zie je op veel pagina’s bovenaan het ‘logootje’ van het betreffende network in het klein. En zo proberen we ook om advertenties niet ‘dwars door een artikel heen’ te plaatsen (alleen de middenpagina’s blijven daarin een uitzonderingspositie houden). Toch is er ook deze keer wel iets substantieels veranderd, en misschien had je dat in een oogwenk gezien of meende je het te voelen. Dit magazine telt nl. 72 pagina’s i.p.v. de gebruikelijke 64; het is dus 12,5% dikker. 2 Zaken hebben daar aanleiding toe gegeven: het groeiende aantal networks en de toegenomen belangstelling om te adverteren. Wat het 1e betreft: ook deze keer wordt weer een nieuwe tak aan de SDN-boom voorgesteld, te weten Core Systems. Rolf Craenen is daar de trekker van en stelt het network en zichzelf voor op pag. 45. En in dit magazine ook al een 1e artikel vanuit het network van de hand van Michelle Cordes en Christiaan Heidema met als titel “The Best of Both Worlds: Modernizing Core Sytems”. Een onderwerp dat alle SDN-ers vast aan zal spreken, en wat ook meteen uiting geeft aan de wens van Rolf om niet een op zichzelf staand onderdeel van het SDN te worden, maar te streven naar zoveel mogelijk integratie en samenwerking met de bestaande networks. Om maar 2 voorbeelden te noemen: architectuur en databases spelen uiteraard ook in de wereld van Core Systems een prominente rol. De 2e aanleiding, het toenemen van de adverteerbereidheid – bestaat dat woord? -, is iets wat we ons voor 1 oktober met groot gemak voor konden stellen. De behoefte aan personeel leek met de dag te groeien. Maar inmiddels hebben we de maand oktober beleefd, en sommigen met een grotere intensiteit dan ze lief was. Blijft echter tot dit moment een feit dat we meer advertenties kwijt konden dan we misschien wilden met het oog op de verhouding tussen informatie en ‘advormatie’. Om te voorkomen dat die verhouding scheef groeit hebben we in ieder geval deze editie uitgebreid tot 72 pagina’s. En de volgende keer zullen we wel weer zien … Die volgende keer is sowieso een bijzondere keer, want dat wordt uitgave nr. 100! Binnen de redactie wordt al druk gebrainstormd over de ideeën om daar iets bijzonders van te maken, maar als een van jullie daar een briljante ingeving voor heeft of b.v. nog heel bijzonder materiaal … laat het ons weten! Voor jullie informatie: magazine nr. 1 dateert van 1 mei 1990, toen het SDN nog Clipper Developers Group Netherlands - CDGN dus – heette. Het tijdschrift droeg de titel “CONFERENCE” met als ondertitel “magazine voor de professionele clipper gebruiker”. Enkele onderwerpen die in die 1e editie aan bod kwamen waren “Netwerken”, “Netwerken en ClipTools”, “Memory Management”, “Bits, bytes en woorden” en “Bus Report”. En dit laatste had niets met openbaar vervoer te maken … Het grappige is dat in dit laatste pre-100 magazine enkele van die onderwerpen nog steeds actueel blijken. Alex Thissen b.v. heeft het over (het programmeren van applicaties voor) netwerken, alleen vertelt hij hoe je dat vandaag de dag kunt doen voor Peer-to-Peer netwerken met .NET Framework 3.5. En Dennis Vroegop laat in zijn artikel “C# Deep Dive” zien dat het ook nu nog verstandig is - en als je je echt wilt onderscheiden van de ‘mindere’ goden zelfs noodzakelijk is – om kennis te hebben hoe de datastructuren binnen je applicatie omgaan met het beschikbare geheugen. Plaatsen we het op de stack of toch op de heap, en wat zijn de spelregels die we daarbij te hanteren hebben? Daarentegen hebben we natuurlijk ook volop nieuw materiaal, zeker in verhouding tot 18 jaar geleden, en gelukkig maar. We willen (ons) natuurlijk jong blijven (voelen). Peter van de Sman b.v. heeft de MP3-tags ontdekt en onderzocht, en doet daar verslag van in zijn artikel “MP3 Tags in Delphi 2009” … hij loopt dus zelfs al 19 jaar voor op nummer 1. En ook Marcel de Vries werpt zijn blik al over de grens van dit jaar met een vooruitblik naar de nieuwe Visual Studio versies, nu nog bekend onder de codename Rosario. M.a.w. ‘oude’ onderwerpen in een nieuwe context, én ‘nieuwe’ onderwerpen in een nieuwe context, maar in ieder geval boeiend en inspirerend om te lezen, zo denken wij! Veel leesplezier! rob willemsen,
[email protected] • magazine voor software development 3
Inhoud 03
Voorwoord
Rob Willemsen
04 05
Inhoudsopgave Peer to Peer Applicaties met .NET Framework 3.5
Alex Thissen
11
MP3 Tags in Delphi 2009
Peter van der Sman
17 19
Agenda 2008/2009 Visual Studio Team System "Rosario"
Marcel de Vries
23
Zeven Windows Communication Foundation Best Practices
Bert Loedeman en Steef-Jan Wiggers
30
C# Deep Dive
Dennis Vroegop
34
Delphi 2009 Taaluitbreidingen: Generics en Anonymous Methods
Bob Swart
42
Portaltechnologie: SharePoint of DotNetNuke?
Nick Boumans
45
SDN EVENT
Core Systems
Rolf Craenen
46
Interesting Things: Increasing Speed
Sander Hoogendoorn
47
Data & Storage 12 december 2008 NBC, Nieuwegein
Silverlight Biedt Nieuwe Mogelijkheden voor SharePoint
Donald Hessing
50
The Best of Both Worlds: Modernizing Core Sytems
Michelle Cordes en Christiaan Heidema
54
Reflection als Inpakpapier
Sander van de Velde
60
Een Eerste Introductie in F#
Piet Amersfoort
65
ASP.NET onder de Motorkap: Controls Maken
Michiel van Otegem
67
BizTalk Server: Architectuur
Steef-Jan Wiggers
.NET C#
Alex Thissen
Peer to Peer Applicaties met
.NET Framework 3.5 Windows XP en Vista bieden ondersteuning voor peer-to-peer netwerken. Peer-to-peer netwerken zijn vooral bekend door de filesharing netwerken, zoals KaZaA en BitTorrent en de applicaties die van deze netwerken gebruik maken. Maar peer-to-peer netwerken en -applicaties bieden meer mogelijkheden dan alleen het delen van bestanden. Zo kun je andere resources delen, bijvoorbeeld de rekenkracht van je machine, of online collaboratie vereenvoudigen. Toch is peer-to-peer nog niet goed bekend bij de meeste .NET ontwikkelaars. Daar komt verandering in: het .NET Framework 3.5 heeft een managed API gekregen voor het bouwen van peer-to-peer en collaboratieve applicaties met behulp van de voorzieningen in het besturingsysteem. In dit eerste artikel over peer-to-peer applicaties kijken we naar peer-to-peer terminology, het PNRP protocol en de bijbehorende managed API plus een simpele toepassing met behulp van de WCF PeerChannel API. Peer-to-peer netwerken Peer-to-peer (P2P) netwerken zijn er in verschillende soorten en maten. Een P2P netwerk is een normaal IP-gebaseerd netwerk. In het netwerk komen peers voor, die ook wel nodes worden genoemd. De nodes kunnen allerlei soorten resources zijn, zoals een applicatie, een gebruiker, een computer of anderssoortige hardware (printer, mobiele telefoon of zelfs een regensensor met IP-adres). De peers in een P2P netwerk zijn met elkaar verbonden. De verzameling van peers die onderling verbonden zijn wordt een mesh of graph genoemd. Het is mogelijk dat een P2P netwerk meerdere meshes bevat, aangezien groepen peers niet met elkaar verbonden hoeven te zijn. In een volledig verbonden mesh hebben peers verbindingen met alle andere peers. Naarmate het aantal peers in een netwerk groter wordt is het steeds moeilijker om rechtstreekse verbindingen te behouden. Het is vaak voldoende om alle peers via-via met elkaar te verbinden. Dit zijn de zogenaamde “partially connected” meshes.
Fig. 1: Verschillende soorten meshes Om met alle ontwikkelaars te kunnen communiceren hoef je niet rechtstreeks met iedereen te praten. Het is voldoende om je boodschap aan een kleine groep kennissen te vertellen, die het doorvertellen aan hun kennissen, enz., totdat iedereen de boodschap heeft gehad. Op eenzelfde manier kun je ook met een enkeling communiceren, al is het direct doorgeven van boodschappen dan gerichter. Het aantal spillen, de topologie en de indeling van het netwerk is afhankelijk van de opzet van het P2P netwerk. Applicaties voor peer-to-peer netwerken De gemiddelde netwerk applicatie bestaat uit clients en servers. Denk aan een database applicatie en database server, of browser en webserver. De client en server hebben daarbij duidelijk verschillende rollen. De client werkt samen met de server, die altijd aanwezig is, een vast en bekend netwerk adres heeft en daarmee makkelijk te bereiken is. De clients communiceren onderling niet rechtstreeks, maar gebruiken de server als tussenpersoon, zoals bij een chat-applicatie en -server. Daardoor hoeven de clients elkaar niet te kennen of te kunnen bereiken. Een applicatie die gebruik maakt van een peer-to-peer netwerk gedraagt zich doorgaans anders. De applicatie meldt zich vanaf de host machine aan op dit netwerk en wordt daarmee een peer in een mesh. De machine heeft doorgaans een wisselend netwerkadres. Verder moeten de clients rechtstreeks met elkaar kunnen communiceren, liefst zonder tussenkomst van centrale servers. De doorsnee handelingen van een P2P applicatie zijn: • Aanmelden op een P2P netwerk De applicatie meldt zich aan op een P2P netwerk en voegt zich bij een bestaande mesh van peers. De nieuwe peer zal één of meerdere verbindingen met peer nodes aangaan. Hierna maakt de applicatie echt deel uit van het mesh. Als er nog geen mesh is, zorgt de applicatie voor het aanmaken van het mesh. • Communicatie met alle peers De applicatie kan via het mesh eenvoudig met alle peers tegelijk communiceren. De applicatie voert dan een broadcast uit over het mesh. De broadcast start met het aanspreken van de directe peers, die de boodschap weer doorspelen naar hun directe peers, enz.
magazine voor software development 5
.NET C#
• Interactie met specifieke peers De applicatie kan ook samenwerken met bewust gekozen peers. Het is soms nl. wenselijk dat er directe communicatie plaatsvindt en niet via-via. De directe interactie is alleen mogelijk wanneer het netwerkadres van de gewenste peers achterhaald kan worden. Daarna kan de applicatie via gangbare mechanismen communicatie starten tussen de peers, bijvoorbeeld met behulp van sockets, ASMX web services of WCF services. De peers van een pure P2P applicatie zijn allemaal gelijkwaardig. De minder pure vormen van P2P applicaties kennen een soort van centralisatie, doordat er dedicated servers in het netwerk zijn die coördinatie en administratie van het netwerk verzorgen. Soms zijn er in het P2P netwerk peers met een speciale functie (ook wel super-peers genoemd) aanwezig. Dit betekent veelal dat er een bestaande infrastructuur nodig is om de P2P applicatie te kunnen laten functioneren. De pure P2P applicaties kunnen juist ad-hoc functioneren. Peer name resolution Microsoft heeft bij de implementatie van peer-to-peer netwerken gekozen voor het IPv6 protocol. Als je nog niet bekend bent met IPv6 kun je op http://technet.microsoft.com meer informatie vinden door te zoeken op “IPv6 technical reference”. De reden om te kiezen voor IPv6 is dat de beschikbare IPv4 adressen (2^32 mogelijkheden) inmiddels schaars zijn, terwijl IPv6 (2^128 mogelijkheden) nog meer dan genoeg adressen beschikbaar heeft voor alle computers, devices en applicaties die er nog gemaakt gaan worden in de nabije toekomst. Dit betekent voor het P2P netwerk dat iedere peer zijn eigen unieke IPv6 adres zou kunnen krijgen.
Beschikbare IPv4 adressen zijn schaars; met IPv6 kan alles en iedereen een adres krijgen De immense hoeveelheid IPv6 adressen en ook mogelijke peers in een P2P netwerk brengt diverse uitdagingen met zich mee voor het P2P netwerk en de applicaties. Ten eerste draait het grootste deel van de huidige netwerk-infrastructuur nog op IPv4, dat standaard gescheiden is van een IPv6 netwerk. Dit probleem heeft Microsoft opgelost met een UDP tunnelling protocol genaamd Teredo. Dit protocol zorgt voor de overgang van een IPv4 netwerk naar het IPv6 netwerk en eventueel nog terug. Zo kunnen resources met een IPv6 adres binnen een IPv4 netwerk communiceren met en via IPv6 netwerken. Verder zal het in een IPv6 gebaseerd netwerk moeilijk zijn om andere peers te vinden zonder gebruik te maken van centrale servers. Microsoft heeft hiervoor het Peer Name Resolution Protocol (PNRP) ontworpen en geïmplementeerd. PNRP is een gedistribueerd en (nagenoeg) serverloos protocol, gemaakt voor schaalbare en betrouwbare naam-publicatie en -resolutie. Het protocol maakt gebruik van PNRP identifiers. Een PNRP ID is een 256 bit unsigned integer, bestaande uit een 128 bit hash van een PNRP peer name (ook wel P2P ID) en een 128 bit gegenereerd nummer voor de service location, dat een indirecte verwijzing is naar de locatie van de peer. Het service location deel in het PNRP ID maakt deel uit van een numerieke namespace waarbij de getallen relatieve “afstanden” voorstellen tussen adressen van de peers. Hiermee kan tijdens een zoektocht naar een specifieke peer steeds dichter naar die peer genavigeerd worden.
6
MAGAZINE
Fig. 2: PNRP (Bron: Microsoft TechNet (http://technet.microsoft.com/en-us/ library/bb726971(en-us).aspx)) Een voorbeeld van een PNRP ID is: 198e25a029dfaef373bd86adac7dd2ef.69b5db7be977e1870aa9233 76f5f5d6e Een PNRP publisher registreert zijn zelfgekozen peer naam en bijbehorende IPv6 eindpunten bij een mesh waar het deel van uit wil gaan maken. Een PNRP resolver kan op basis van de peer naam of het PNRP ID op zoek gaan naar een specifieke peer en daarvan de geregistreerde eindpunten achterhalen. Het elegante van het PNRP protocol is de gedistribueerde cache van PNRP IDs die wordt bijgehouden door alle geregistreerde peers. Iedere peer houdt een meerlaagse cache bij van geregistreerde IDs. Tijdens het resolven van een peer naam wordt er via de caches van peers gezocht naar de uiteindelijke locatie van de registrerende peer. Laten we dit nog eens vergelijken met het zoeken naar een specifieke .NET ontwikkelaar. Stel je bent op zoek naar een zekere programmeur in Leeuwarden, terwijl je zelf in een ander deel van Nederland zit. Je kent alleen de naam van die programmeur en hebt geen adres. Je kunt dan je directe kennissen vragen of zij die persoon kennen. Zij kennen (in hun eigen adressenlijst c.q. cache) wel een aantal personen, maar waarschijnlijk niet die ene persoon. Als zij iemand kennen die meer in de buurt woont, bijvoorbeeld in Drenthe of Friesland, dan geven zij het adres van die persoon door. Vervolgens kun je aan die persoon vragen of deze de gezochte persoon kent. Zo kom je alsmaar dichterbij en vind je uiteindelijk hopelijk de juiste persoon. Een programmeur hoeft in dit scenario niet alle anderen te kennen. Het is voldoende om binnen je eigen straat, stad en je provincie een relatief klein aantal personen te kennen. Dit komt overeen met de meerlaagse cache van PNRP. Het maakt het PNRP protocol schaalbaar voor grote aantallen peers, zoals in de orde van tientallen of honderden miljoenen. Verder krijgt iedere PNRP peer een unieke DNS naam eindigend op pnrp.net, waarmee je via het netwerk overal te vinden en bereiken bent. Dit geldt zowel voor applicaties als andere resources die gebruik maken van PNRP. Zo kan je computer zich ook registreren en een DNS naam krijgen. Een dergelijke naam heet een Windows Internet Computer Name (WICN). PNRP onderscheidt een aantal clouds waarbinnen geregistreerde peers zich bevinden. Een cloud wordt gevormd door de peer nodes die zich daarin bevinden. PNRP heeft clouds op basis van het type IPv6 adres. Deze soorten clouds zijn: • Global: een globale cloud voor alle peers met een globaal IPv6 adres, dat geldig is op het gehele Internet. • Link-local: locale (intra-net) cloud met peers die een zelf-toegewezen IPv6 adres hebben. De term link-local slaat op lokale netwerk-link (link = interface card), oftewel een lokaal netwerk via een specifieke netwerkkaart. Je kent
.NET C# de link-local adressen waarschijnlijk wel van IPv4, die beginnen met 169.254.*.*. Deze adressen worden door de netwerklaag toegewezen aan netwerkkaarten die geen DHCP server kunnen vinden om automatisch een IP adres te verkrijgen. Zo kunnen computers in een netwerk zonder DHCP toch met elkaar communiceren via het link-local netwerk. Eenzelfde principe wordt gehanteerd bij IPv6. De meeste infrastructuren hebben nog geen manier om lokale IPv6 adressen uit te delen. De netwerkkaarten krijgen daarom een linklocal IPv6 adres beginnend met fe80:: toegewezen via het Neighborhood Discovery process. De grote beperking van link-local adressen is het lokale karakter: routers kunnen link-local verkeer niet doorsturen buiten de link. • Site-local: Deze cloud wordt niet meer gebruikt. De clouds zijn vooral relevant voor de scope van een geregistreerde peer naam. De peer namen zijn alleen geldig en vindbaar binnen een cloud. De link-local cloud is dus alleen interessant voor peer-to-peer applicaties die in een lokaal netwerk gebruikt gaan worden. Er zijn veel meer mogelijkheden in de global cloud, aangezien daarin peers uit het hele Internet voorkomen. Dit betekent dat peers niet op dezelfde lokale netwerk-link hoeven te zitten om met elkaar verbonden te zijn. Een applicatie of resource die zich wil aanmelden op de PNRP global cloud moet allereerst een IPv6 adres hebben. Mocht de infrastructuur IPv6 voorzieningen hebben, dan is dit eenvoudig geregeld. Echter, meestal is het interne netwerk dat verbonden is aan het globale Internet een IPv4 netwerk en niet in staat om IPv6 adressen toe te wijzen. Het eerder genoemde Teredo protocol kan naast tunnelen van IPv4 naar IPv6 ook IPv6 adressen uitdelen aan Teredo clients. Deze adressen zijn te herkennen aan de 2001::0000::/32 prefix. PNRP en het .NET Framework 3.5 Het .NET 3.5 Framework heeft een assembly System.Net.dll met daarin de namespaces System.Net.PeerToPeer en System.Net.PeerToPeer.Collaboration. We zullen in eerste instantie kijken naar de eerste namespace. De System.Net.PeerToPeer namespace bevat allerhande typen voor het werken met PNRP. Zo kun je bijvoorbeeld alle beschikbare PNRP clouds op een systeem als volgt enumereren: CloudCollection clouds = Cloud.GetAvailableClouds(); foreach (Cloud cloud in clouds) {
}
Console.WriteLine("Cloud {0} with scope {1} (ID={2})", cloud.Name, cloud.Scope, cloud.ScopeId);
Ofschoon de eerder genoemde PNRP IDs een prominente rol binnen het protocol spelen, zal een applicatie zich alleen bemoeien met de peer naam. Een peer-to-peer applicatie die zichzelf vindbaar wil maken via PNRP zal zijn peer naam registreren. Een peer naam bestaat uit een autoriteit en classifier in de vorm {authority}.{classifier}. De classifier is een zelfgekozen en meestal makkelijk te lezen naam. De autoriteit wordt automatisch bepaald en geeft aan of de peer naam een secure of unsecure naam is. Unsecure peer namen geven geen garantie dat de naam authentiek is en zijn daarmee makkelijk te spoofen. De autoriteit is dan altijd 0. De secure namen daarentegen gebruiken een public/private key pair voor het berekenen van een hash voor de autoriteit. Het bezit van de private key bewijst dat de publisher daadwerkelijk eigenaar is van de peer naam. Dit zijn een tweetal voorbeelden van peer namen: • 0.LX (unsecure) • 8249799f11314d1d3afea14b5e2ec32bf59d8574.LX (secure) Registratie van peer namen Het registreren van een peer naam gaat als volgt:
PeerName name =
new PeerName("LX", PeerNameType.Unsecured);
PeerNameRegistration registration = new PeerNameRegistration(
name, 1337, Cloud.AllLinkLocal);
registration.UseAutoEndPointSelection = false; IPAddress address =
IPAddress.Parse("IPv4 or IPv6 address");
IPEndPoint endPoint = new IPEndPoint(address, 1337); registration.EndPointCollection.Add(endPoint);
registration.Comment = "GameStats version 1.0.23.110"; registration.Data =
Encoding.UTF8.GetBytes("LX game statistics: ...");
registration.Start();
Het PeerName object wordt geconstrueerd met de classifier “LX” en er is gekozen voor een niet secure peer naam. Vervolgens wordt de registratie uitgevoerd via de PeerNameRegistration klasse. Hierin wordt de peer naam meegegeven, een poort die gebruikt wordt voor de registratie en communicatie met de PNRP peers en de clouds waarin registratie moet plaatsvinden. Dit kan de global cloud of alle link-local clouds (zoals in het voorbeeld) zijn, alle beschikbare clouds of een specifieke selectie van beschikbare clouds. De registratie van een peer name moet ertoe leiden dat de registrerende resource gevonden kan worden op een eindpunt. Deze eindpunten zijn IPv4 of IPv6 adressen met een bijbehorende poort. De registratie heeft daarom een collectie van IPEndPoint objecten nodig die gekoppeld gaan worden aan de peer naam. Tenslotte is het nog mogelijk om commentaar en data te bundelen met de geregistreerde naam. Het commentaar is bedoeld als een korte tekst van maximaal 39 karakters. De data mag een 4 KB grote byte array zijn en kan daarmee bijvoorbeeld geserializeerde objecten bevatten. Beide stukken informatie zouden moeten helpen om de juiste peer naam te selecteren. De registratie wordt uitgevoerd bij de aanroep van de Start methode van het PeerRegistration object. De peer naam is daarna voor het betreffende AppDomain geregistreerd. Andere applicaties kunnen eventueel dezelfde peer naam registreren, maar blijven uniek vindbaar. Resolven van peer namen De volgende logische stap is het vinden van een specifieke peer met behulp van PNRP. Het zoeken naar een peer kan alleen vanuit een ander AppDomain dan diegene die heeft geregistreerd. De Peer NameResolver klasse probeert een peer name te vinden. De resolutie kan synchroon of asynchroon uitgevoerd worden. Het is aan te raden om de asynchrone variant te gebruiken, aangezien een peer name resolutie enige tijd in beslag kan nemen en de synchrone aanroep in die tijd blokkeert. Het onderstaande fragment toont hoe je asynchroon een peer name kunt proberen te achterhalen. PeerNameResolver resolver = new PeerNameResolver(); resolver.ResolveCompleted += (sender, e) => {
foreach (PeerNameRecord record {
};
}
in e.PeerNameRecordCollection)
Console.WriteLine("Peer {0} at address {1}",
record.PeerName, record.EndPointCollection[0]);
resolver.ResolveProgressChanged +=
(sender,e) => Console.WriteLine(e.ProgressPercentage);
magazine voor software development 7
.NET C# Console.Write("Enter unsecure peer name: "); string nameToResolve = Console.ReadLine(); PeerName name =
new PeerName(nameToResolve, PeerNameType.Unsecured);
resolver.ResolveAsync(name, nameToResolve);
Console.WriteLine("Waiting Press ENTER to quit"); Console.ReadLine();
De twee lambda expressions zijn de delegate methoden die worden aangeroepen bij de events ResolveProgressChanged en ResolveCompleted. Deze methoden hebben EventArgs die de voortgang van de resolutie dan wel de gevonden records bij de gezochte peer naam aangeven. De resolutie start bij de aanroep van ResolveAsync. De applicatie zal daarna wachten tot de operatie is voltooid. Het blokkeren van de main thread hoeft niet persé. Een WinForms of WPF applicatie kan doorgaan met het afhandelen van UI events. Het wachten tot de resolutie klaar is kan ook gedaan worden met een multithreading primitieve, zoals het AutoResetEvent. Bouwen van P2P applicaties Na al deze informatie vraag je jezelf wellicht af hoe hiermee een P2P applicatie gebouwd kan worden. P2P applicaties kunnen PNRP inzetten om meshes van peers te vormen binnen een PNRP cloud. Dit kan door slim gebruik te maken van geregistreerde peer namen. De applicatie kan met de peer namen de adressen van andere peers achterhalen en zelf de connecties met de andere peers regelen volgens een gekozen algoritme. P2P applicaties met WCF PeerChannel Een deel van de Windows Communication Foundation stack helpt je om P2P applicaties te maken. De PeerChannel klassen werken met meshes van peers in een P2P netwerk. WCF regelt en onderhoudt de connecties tussen de peers in het mesh en zorgt ervoor dat het mesh intact blijft. WCF gebruikt standaard PNRP om peer namen te registreren en later te kunnen resolven. Alle applicaties die het PeerChannel gebruiken, registreren dezelfde applicatie-specifieke unsecure naam middels PNRP. Hierna kan WCF alle peers in het mesh eenvoudig achterhalen door deze ene naam te resolven. De beveiliging van het WCF mesh wordt niet met behulp van secure peer namen geregeld. Immers, iedere peer zou dan een naam met een verschillende autoriteit hebben, die niet bekend is bij de andere peers. In plaats daarvan beveiligt WCF de toegang tot het mesh met behulp van een wachtwoord of een certificaat. Dit gebeurt indirect, aangezien iedereen een peer naam kan registreren in het mesh en PNRP geen beveiliging kent. Een nieuwe peer in het mesh moet verbinding maken met een aantal nabij gelegen peers. Tijdens het opbouwen van de verbinding vindt authenticatie plaats middels het wachtwoord of het certificaat. De communicatie van de boodschappen tussen de peers is ook te beveiligen met encryptie op het transport of de boodschap. PNRP stelt de nodige eisen aan de inrichting van het netwerk, zoals IPv6 en globale internet connectie. WCF kan daarom ook custom resolvers gebruiken voor het achterhalen van de peers in het mesh. De CustomPeerResolver Service is een custom resolver implementatie die wordt meegeleverd met WCF. Wanneer deze resolver service wordt gehost, kunnen PeerChannel gebaseerde applicaties de resolver gebruiken. De P2P applicatie is daardoor niet meer afhankelijk van PNRP, maar verliest zijn server-loze karakter. Voorbeeld applicatie met WCF Laten we eens kijken hoe met WCF een heel eenvoudige server-loze P2P chat applicatie gebouwd wordt. Allereerst definiëren we een interface met één enkele operatie voor het versturen van een chat
8
MAGAZINE
boodschap naar alle peers in het P2P mesh. De operatie is one-way, aangezien er een broadcast gedaan wordt over het mesh en er geen antwoorden verwacht worden. Dit is typisch voor alle operaties van PeerChannel. Verder zullen de peers gebruik maken van een duplex channel, omdat ze zowel boodschappen ontvangen en ook op eigen initiatief boodschappen gaan versturen. Een duplex channel moet een service contract hebben dat een callback contract heeft. Het callback contract is hetzelfde gehouden als het gewone contract, aangezien de rol van iedere peer symmetrisch is: verzenden en ontvangen. [ServiceContract
(Namespace="urn:www-sdn-nl:services:p2p:chat", CallbackContract=typeof(IChat))]
public interface IChat {
}
[OperationContract(Name = "Say", IsOneWay = true)] void Say(string text);
De implementatie van de service is simpelweg het schrijven van een binnenkomend bericht naar de console. class ChatClient: IChat {
#region IChat Members public void Say(string text) {
} }
Console.WriteLine("{0}: {1}",
DateTime.Now.ToLongTimeString(), text);
#endregion
Nu zal het nodig zijn om een proxy aan te maken, zodat de services met elkaar kunnen communiceren. Wellicht dat het nu enigszins vreemd lijkt te worden. Immers, in een puur P2P netwerk komen alleen maar clients voor, maar nu hebben we het toch over een service met een proxy (oftewel client). In het geval van PeerChannel moet je dit wat ruimer zien. De implementatie van de service is die van de peer client. De proxy is de geijkte WCF manier om met een service te communiceren, ook al is dat in P2P een andere instantie van dezelfde service implementatie. Bedenk dat de service is feite een peer is, oftewel een client in het P2P netwerk. Alle peers zijn allemaal “servers” op het moment van een broadcast naar het mesh. Zij ontvangen allemaal de binnenkomende “client” boodschap.
In een puur P2P netwerk komen alleen maar clients voor Jammer genoeg heeft het PeerChannel nog geen ondersteuning voor tooling als svcutil.exe en wsdl.exe. Het is dus niet mogelijk om met die tools een WCF proxy te maken. De benodigde code is ook met de hand te schrijven. De volgende interface zorgt voor een client channel waarmee de DuplexChannelFactory een proxy kan instantiëren. public interface IChatChannel: IClientChannel, IChat {}
We maken verder geen volledige proxy klasse, maar laten de WCF infrastructuur een dynamische proxy genereren. Hieronder staat de code van de console applicatie die de WCF chat service gaat hosten.
.NET C# class Program { static IChatChannel channel; static void Main(string[] args) { ChatClient client = new ChatClient(); InstanceContext context = new InstanceContext(client); DuplexChannelFactory
factory = new DuplexChannelFactory (context, "ChatClientEndPoint"); factory.Credentials.Peer.MeshPassword = "123"; channel = factory.CreateChannel(); PeerNode node = channel.GetProperty(); channel.Open(); Console.WriteLine("Listening on port {0}", node.Port); Console.WriteLine("Online? {0}", node.IsOnline);
}
}
Het ABC van de client configuratie laat het volgende zien: • Adres: Het scheme van het netwerkadres is net.p2p voor PeerChannel. Het host-adres definieert niet zozeer een bestaand IPv4 of IPv6 adres, maar de naam van het mesh waar de peer deel van gaat uitmaken. • Binding: De binding voor PeerChannel is de netPeerTcpBinding. De bijbehorende binding-configuratie laat zien hoe in dit geval de beveiliging ‘transport met wachtwoord’ is en dat de resolutie van peers met PNRP wordt gedaan. De poort die op 0 is ingesteld geeft aan dat iedere peer zelf een beschikbare poort mag kiezen om te luisteren naar inkomend verkeer. De implementatie van de console applicatie laat zien wat de poort daadwerkelijk is geworden. • Contract: Het IChat interface vormt het service contract. Hieronder is de applicatie in werking te zien. Negeer het schoonheidsfoutje van de ontvangende clients die op de Say:> regel ook de ontvangen boodschappen tonen.
while (true) { Console.Write("Say:> "); string text = Console.ReadLine(); channel.Say(text); }
Veel van de bovenstaande code is WCF specifiek. De dikgedrukte statements laten aspecten van het P2P programmeren met WCF zien. Allereerst valt te zien hoe het wachtwoord voor de peer authenticatie in het mesh opgegeven wordt. Verder kan de service de peer node opvragen en daarmee achterhalen wat de poort is waarop de service gaat luisteren naar binnenkomend verkeer en of de node online is. Dat laatste verdient enige uitleg. Een peer in een mesh wordt gezien als online wanneer er meer dan één peer aanwezig is. Indien er maar één peer in het mesh is noemt men deze offline, ook al is er op dat moment wel degelijk een netwerkconnectie. Tenslotte bevat de while loop de aanroep van de service-operatie. Deze ene aanroep via de proxy vanuit de peer zal ertoe leiden dat alle peers (inclusief de peer zelf) in het mesh deze aanroep krijgen. Een deel van de configuratie van de PeerChannel service staat in de app.config. Hierbij is het opvallend dat er alleen een is geconfigureerd. Echter, in lijn met de eerdere opmerkingen, was dit te verwachten. <system.serviceModel>
<endpoint name="ChatClientEndPoint" binding="netPeerTcpBinding"
bindingConfiguration="PeerTransportBinding" address="net.p2p://MeshName:1337/client" contract="WcfPeerChannelDemo.IChat" />
<security mode="Transport">
Fig. 3: Console applicatie running Merk op hoe de eerst gestarte chat client (bovenste in de figuur) aangeeft offline te zijn bij het opstarten. De andere clients geven aan online te zijn. Er zijn nog meer mogelijkheden met de PeerChannel API, zoals • het luisteren naar statusveranderingen van een peer node; • het gebruik van propagatiefilters die bepalen welke boodschappen doorgelaten worden; • beperken van de broadcast door het limiteren van de hop-count (aantal nodes waarnaar gesprongen wordt); • het schrijven van een eigen custom resolver. Het PeerChannel in WCF werd al in versie 3.0 van het .NET Framework geïntroduceerd. De managed API voor PNRP zoals die in .NET 3.5 zit, was toen nog niet beschikbaar. Vandaar dat WCF een eigen implementatie heeft. Nu de System.Net.PeerToPeer namespace beschikbaar is, kan iedere ontwikkelaar die een P2P applicatie wil maken en daarbij een mesh wil inzetten gebruik maken van PNRP om dat te realiseren. Samenvatting De eerste stap bij het ontwikkelen van P2P applicaties is het kiezen van een geschikt P2P netwerk. Microsoft biedt met IPv6 en het PNRP protocol een goede infrastructuur om op te bouwen. Een applicatieontwikkelaar die P2P features in wil bouwen in zijn applicatie zal dus in contact komen met nieuwe protocollen als IPv6, Teredo en vooral PNRP. De managed APIs van het .NET Framework 3.5 bieden voorzieningen om met PNRP te werken en zo peers in een P2P netwerk te publiceren en te vinden en zodoende meshes te creëren. Tenslotte kun je met behulp van je favoriete communicatiemechanisme peers met elkaar laten samenwerken. Je grootste uitdaging voor het bouwen van een P2P applicatie is het bedenken van een goede toepassing. • Alex Thissen is .NET trainer en coach werkzaam bij Class-A in Woerden.
magazine voor software development 9
Advertentie Bergler Nederland b.v.
DELPHI
Peter van der Sman
MP3 Tags in Delphi Je hebt zo van die momenten dat alles vanzelf op zijn plaats valt. Zo kocht ik afgelopen zomer een nieuwe MP3 speler. En die liet mij allerlei informatie zien die ik nog niet kende. Natuurlijk had ik bij het afspelen op de computer al wel eens gemerkt dat er soms wat merkwaardige titels voorbij kwamen, maar nu zag ik ineens bij bepaalde MP3’s ook plaatjes op mijn display verschijnen. Tijd dus om uit te zoeken hoe dat zit en hoe je die informatie kunt wijzigen, aanvullen of verwijderen. Bijna op dezelfde dag kreeg ik een testlicentie voor Delphi 2009. Dus waarom niet het nuttige met het aangename verenigen en met de nieuwe Delphi iets maken om mijn mp3’tjes op te schonen.
www.id3.org levert de standaard voor al de toegevoegde informatie die officieel ID3 tags heten Gelukkig hoef je dat niet allemaal zelf te verzinnen. Naast mp3’s kun je op internet ook eenvoudig achterhalen hoe het allemaal in elkaar steekt. Een belangrijke bron is de website www.id3.org. Die levert de standaard voor al de toegevoegde informatie die officieel ID3 tags heten. Het is ook een goed startpunt voor allerlei voorbeeldprogramma’s, al dan niet met broncode. Maar deze broncode zal niet zomaar gaan werken onder Delphi 2009. En ze maken zeker geen gebruik van de nieuwe mogelijkheden die Delphi 2009 ons biedt. Dit artikel beschrijft een implementatie die zeker wel werkt onder Delphi 2009. Het MP3 format Voordat we aan de slag kunnen moeten we natuurlijk eerst weten hoe een MP3 bestand in elkaar steekt. De algemene opzet is eenvoudig: • ID3v2 Tag (versie 2) • Audio data • ID3v1 Tag (versie 1). Zoals gezegd, heel eenvoudig. Voor de eigenlijke (audio) data kan informatie zijn geplaatst en erachter ook, waarbij opgemerkt moet worden dat dit optioneel is. Alleen audio-data of audio-data met maar één tag mag ook. En ja hoor, strikt genomen mag ook de audio-data ontbreken! En dat kan nog zinvol zijn ook: er bestaat een testset met
2009
tags waarmee het lezen van de (ID3v2) tags kan worden getest. En die testbestandjes hebben geen audio nodig! Wat we gaan doen is een TMP3file-class maken om van daaruit de verschillende onderdelen te benaderen. In eerste opzet ziet dat er dan als volgt uit: Type TMP3file = class V1tag : TID3v2Tag; Mpeg
: TMpegData;
V2tag : TID3v2Tag;
Procedure LoadFromFile(aFilename:string); Procedure SaveToFile
end;
(aFilename:string);
Later zal ik daar nog op terugkomen. We beginnen met de drie verschillende onderdelen van het bestand, maar eerst... Een MP3Toolkit Meestal loop je bij een implementatie tegen allerlei (bij)zaken aan die opgelost moeten worden. Zo krijgen je bij het lezen van tags te maken met een aantal tabellen (bijvoorbeeld, genres, soorten plaatjes, toegestane talen) en wat specifieke rekenkunde. Om dat niet steeds ad-hoc op te hoeven lossen kies ik ervoor dat allemaal in een aparte unit te stoppen. En binnen die unit definieer ik dan ook (de meeste) types die gebruikt gaan worden. Bovendien kies ik ervoor de algemene functies en procedures in een aparte class te stoppen. Dat maakt het wat duidelijker wat allemaal bij elkaar hoort. Dus als ik het later heb over een tabel of functie uit de toolkit, weet u nu waar ik het over heb. Ik zal u de verdere beschrijving besparen, alle details kunt u vinden in de download. De ID3v1 Tag De ID3v1 tag is qua opzet eenvoudig. De definitie dateert van 1996. Het is een blok van 128 bytes dat achter aan het bestand wordt geplakt. De indeling ligt helemaal vast: type
TID3v1Data = record ID
Title
: array [1..3] of AnsiChar;
: Array [1..30] of AnsiChar;
Artist : Array [1..30] of AnsiChar; Album Year
: Array [1..30] of AnsiChar; : array [1..4]
of AnsiChar;
Comment: Array [1..30] of AnsiChar; Genre
end;
: Byte;
Op het internet wemelt het van de voorbeeldprogramma’s om de ID3v1 Tag te lezen (en te schrijven). En de meesten zullen niet meer correct werken onder Delphi 2009. Want het chartype is nu een 2-bytes type terwijl in de definitie toch echt het 1-byte type wordt bedoeld. Vandaar ook dat ik in de definitie specifiek het type AnsiChar gebruik (in alle voorgaande versies van Delphi was dit het standaard type).
magazine voor software development 11
DELPHI
Er zijn een paar kleine details: • Genre. Het Genre is een byte. En die verwijst naar een standaardtabel met genres, die is opgenomen in de eerder genoemde toolkit. • Tracknummer. Optioneel kan een tracknummer worden toegevoegd. Daar is eigenlijk geen plaats voor in de datastructuur, maar in dat geval worden de laatste twee bytes van het veld “comment” opgeofferd. Als Comment[29] een #0 is dan vinden we in Comment[30] het tracknummer (als byte). Lezen en Schrijven Om de ID3v1 tag te lezen hoeven we dus alleen de laatste 128 bytes van het bestand te lezen. Als de header klopt, dat wil zeggen als het ID dat we lezen “TAG” is, dan gaan we ervanuit dat het inderdaad een versie 1 tag is. En als het niet klopt, dan is er dus geen versie 1 tag in het bestand aanwezig. Bij het schrijven van een ID3v1 tag kijken we eerst of er al een tag in het bestand is opgenomen. Zo ja, dan overschrijven we die; anders plakken we hem eenvoudig weg achter aan het bestand. Het is niet de bedoeling eindeloos tags achter het bestand te plakken. Karakterset of encoding Het mag duidelijk zijn dat de gedachte achter de ID3v1 tag is dat alles in ASCII karakters zou worden geschreven. Een meestal is dat ook zo (ik ben nog geen echte voorbeelden van het tegendeel tegengekomen). Een probleem is wel welke ASCII-tabel is gebruikt. De liefhebbers van de Nederpop zullen er niet zo snel tegenaan lopen, maar als je per ongeluk een liefhebber bent van Griekse muziek dan loop je toch een goede kans dat de tag is aangemaakt op een machine met een Griekse codepage. En als een Griek het over “Mikis Theodorakis” heeft, dan schrijft hij “Μίκης Θεοδωράκης”, wat wij dan weer te zien krijgen als “Ìßêçò ÈåïäùñÜêçò”. Op zich kunnen we dat nog wel regelen, we kunnen Windows opdragen met de Griekse codepage te gaan werken, maar als we ook nog liefhebber zijn van Russische muziek, wordt dat toch een moeizaam verhaal. Gelukkig kunnen we dat probleem nu afvangen … als we tenminste weten in welke codepage de data is geschreven. In SysUtils vinden we het nieuwe TEncoding type. En een Encoding is niets meer of minder dan een verzameling procedures waarmee je een aantal stringmanipulaties kunt doen met als uitgangspunt de bijbehorende karakterset/codepage. De functie die wij nodig hebben is GetString. Deze functie verwacht als eerste een parameter van het type TBytes (=array of byte). Optioneel kunnen ook nog een startindex en een lengte worden opgegeven. Helaas kun je een “array [1..30] of AnsiChar” niet typecasten naar een TBytes, dus daar moeten we iets voor verzinnen. De code is als volgt:
Het schermpje ziet er als volgt uit.
Fig. 1: Voorbeeld voor een ID3v1 tag in de standaard tekstcodering Het vinkje geeft aan of er een Tag in het bestand is opgenomen (bij lezen). Bij het schrijven bepaalt het vinkje of de Tag al dan niet in het bestand wordt geschreven (eventueel wordt de tag dan verwijderd). Verder is het (deel)scherm een rechttoe rechtaan weergave van de datastructuur. In dit voorbeeld zien we tamelijk onleesbare teksten verschijnen. Dat komt omdat ik, bij wijze van voorbeeld, teksten in afwijkende coderingen heb geschreven. En om het nog erger te maken: ik heb zelfs voor ieder veld een verschillende codering gebruikt, een situatie die je in het echt niet tegen zult komen. Maar hier is het zinvol want het toont wat er zoal gebeurt. De teksten kunnen weer “leesbaar” gemaakt worden door de juiste codering te selecteren. Na voor “Grieks” te hebben gekozen ziet het scherm er als volgt uit:
GRencoding:=TEncoding.GetEncoding(1253);
Fig. 2: Voorbeeld voor een ID3v1 tag met de Griekse tekstcodering
system.Move(ID3v1Data.Artist[1], lBytes[0],aSize);
Mikis Theodorakis is nu weer leesbaar (als je tenminste Grieks kunt lezen). De overige teksten worden behandeld alsof ze met de Griekse tekstcodering zijn geschreven. Het lijkt voor u misschien op Grieks, maar dat is het niet. Een Griek zou de titel lezen als “Pxrrth[], Axlgipthp[], Aelipsro” en dat lijkt ook voor hem nergens op. Om die teksten te kunnen “lezen” moeten we overschakelen naar de andere coderingen. Later zullen we nog zien welke.
SetLength(lBytes,30);
MyTekst:=GRencoding.GetString(lBytes,0,30)
En zo krijgen we onze befaamde musicus weer te zien als een, in ieder geval voor een Griek, leesbare Griek. Het is overigens heel verleidelijk de laatste drie regels te vervangen door een enkele regel waarin de “artist” wordt getypecast naar een TBytes, dus: MyTekst:=
GRencoding.GetString(TBytes(ID3v1Data.Artist),0,30)
Maar zoals aangeven, het werkt echt niet. Het compileert uitstekend maar levert gegarandeerd een runtime-error op! In de implementatie heb ik op het scherm een comboboxje gezet waarin een aantal coderingen is opgenomen (zie verderop). De gekozen Encoding is de encoding die gebruikt wordt bij lezen en schrijven.
12
MAGAZINE
Over Encodings Vroeg of laat loop je tegen de vraag aan welke encodings er eigenlijk allemaal bestaan. In het generieke TEncoding-type worden enkele encodings gedefinieerd. Dit zijn class variabelen. Dat wil zeggen dat als je deze gebruikt ze maar één keer aangemaakt worden. Andere karaktersets moet je expliciet zelf aanmaken met de functie TEncoding.GetEncoding(Codepage:integer);
De meest voorkomende codepages zijn de nummers 1250 (ANSII Europees) tot 1258 (ANSII Vietnamees). Die kunnen we dus eenvoudig
DELPHI
aanmaken. Maar er zijn er nog veel meer (op mijn systeem zijn het er 53). Om die allemaal te kunnen vinden kun je de procedure Enum SystemCodePages gebruiken. Die doet precies wat de naam belooft. Je vindt hem in de Windows Unit. EnumSystemCodePages(@CallBackProc,cp_Supported);
In plaats van cp_Supported kun je ook cp_Installed gebruiken, bij mij maakt het geen verschil. De CallBackProc ziet er als volgt uit: function CodePagesProc(aCodePageString:PChar): Cardinal; stdcall;
var
lStr:String;
lCodePage:integer;
begin
lStr:=StrPas(aCodePageString); lCodePage:=StrToInt(lStr); // DOE ER IETS MEE
end;
Dan is er nog een klein detail. Met GetEncoding krijgen we wel de Encoding die hoort bij de betreffende CodePage, maar welke het is weten we, afgezien van het nummer, niet. Gelukkig kunnen we daar wel achterkomen door dit netjes aan Windows te vragen: Function GetCodePageName(aCodePage:integer):String; Var
lpCPInfoEx: CPINFOEXW;
begin if
end;
GetCPInfoExW(lCodePage,0,lpCPInfoEx) then
result:= lpCPInfoEx.CodePageName;
De CPINFOEXW structuur bevat ook andere informatie, bijvoorbeeld het MaxCharSize. De TEncoding gebruikt dit intern om het veld “IsSingleByte” te vullen. Helaas gebruikt Codegear deze informatie niet om de Encoding meteen maar een zinvolle caption te geven. De MPEG data Het is niet de bedoeling van dit artikel om te beschrijven hoe je zelf de audio-data kunt manipuleren. Maar op internet vond ik wel de informatie [2] hoe je de basisinformatie weer kunt geven. Deze code heb ik integraal in het demoprogramma opgenomen. Op het scherm zien we na het lezen van een MP3-bestand de gegevens over bitrate, etc.
Hiermee kan, praktisch gezien, onbeperkt informatie toegevoegd worden aan het bestand. De ID3v2 tag wordt aan het begin van het bestand geschreven. Om te weten of er een ID3v2 tag aanwezig is lezen we de eerste 10 bytes van het bestand. Als dat een geldige ID3v2-header is dan kunnen we de header verder lezen; zo niet, dan nemen we aan dat er geen ID3v2 header aanwezig is. De algemene structuur van de ID3v2 tag is als volgt: • Header • Extended Header • frame 1 • ….. • frame n • Footer • Padding We zien dat de tag vooral bestaat uit één of meer frames. Afhankelijk van de versie (er bestaan drie versies van ID3v2) kan er ook nog “Extended Header” informatie en/of een “Footer” zijn opgenomen.
De gedachte achter “Padding” is de volgende: omdat de ID3v2 tag aan het begin van het bestand is geplaatst moet in principe bij iedere kleine wijziging het hele bestand opgeschoven worden En dan is er nog “Padding”. De gedachte achter “Padding” is de volgende: omdat de ID3v2 tag aan het begin van het bestand is geplaatst moet in principe bij iedere kleine wijziging het hele bestand opgeschoven worden. Om dat te voorkomen kan/mag aan het einde van de ID3v2 tag een aantal nullen worden geschreven. Intermezzo De eerste versie ID3v2 is versie 2.00 en dateert uit 1998. Deze is in 1999 vervangen door id3v2.3.0. In 2000 is deze definitie in id3v2.4.0 aangepast. Het zijn echter maar relatief kleine wijzigingen die vooral betrekking hebben op de header en footer. In de praktijk blijkt vooral versie 2.3 gebruikt te worden. Bij mij thuis heb ik maar een paar bestanden met 2.4 informatie gevonden. Maar dat kan natuurlijk ook gewoon komen omdat de meeste van die bestanden afkomstig zijn van mijn eigen CD’s en LP’s en aangemaakt zijn met hetzelfde programma… Het TID3v2Tag type Aangezien de versie2 tag feitelijk een lijst met frames is, kunnen we de TList goed gebruiken als basis. Of beter natuurlijk de TObjectList. Dan hoeven we ons geen zorgen meer te maken over het weer vrijgeven van de frames na gebruik. Standaard gebruik ik een afgeleide van TObjectList met twee extra functies:
Fig. 3: De Mpeg gegegens uit het voorbeeldbestand De ID3v2 tag Het hoeft geen betoog dat de informatie die in de ID3v1 tag gestopt kan worden beperkt is. Daarom is in 1998 ID3v2 tag gedefinieerd.
Type TMyObjectList=class(TObjectList) Protected Function CreateItem:pointer; virtual; d Public Function AddNewItem:pointer; End;
De functie CreateItem is virtual zodat deze eenvoudig in afgeleiden aangepast kan worden. Overigens gebruik ik zelden private proper-
magazine voor software development 13
DELPHI
ties of procedures. Niets zo frustrerend als gedwongen worden hele lappen codes te dupliceren om ergens een detail te kunnen wijzigen. De ID3v2 Header Gelukkig heeft de header van de ID3v2 tag voor alle versies dezelfde opbouw: TID3v2Header = record ID: array[1..3] of AnsiChar; Version: Byte; Revision: Byte; Flags: Byte; TagSize: TInt28; end;
d
Ook hier moeten we het ID weer specifiek als AnsiChar definiëren. Version en Revision zijn wat ze lijken te zijn. In de Flags vinden we informatie over het wel of niet aanwezig zijn van een Extended header en/of een Footer, wat dan weer wel afhankelijk is van de versie. De Tagsize geeft ons de totale omvang van de tag (inclusief de header). Dat is een “safe sync integer”. Ik geef het toe, ik had er ook nog nooit van gehoord, maar het blijkt een integer te zijn waarbij steeds bit 8 wordt overgeslagen. Om daar weer een “gewoon” getal van te maken moeten we wat met bits gaan schuiven. In de MP3toolkit is daar een functie Int28ToInt32 voor opgenomen.
Volgens de definitie mag de informatie uit de extended header desgewenst geskipt worden In het voorbeeldprogramma heb ik een functie gemaakt om de header te decoderen. Deze functie geeft true terug als er een header wordt aangetroffen en false als dat niet het geval is. Als er een header is, dan wordt de position in de stream aan het begin van de lijst met frames gezet zodat we direct kunnen gaan lezen. Bij het interpreteren van de header wordt de eventuele Extended Header ingelezen en apart gezet. Deze kan dan eventueel later weer ongewijzigd worden geschreven. Het demoprogramma doet verder niets met deze informatie. Dat is volledig legaal: volgens de definitie mag de informatie uit de extended header desgewenst geskipt worden. ID3v2 frame Alle frames bestaan uit een frame-header gevolgd door een aantal bytes content. Op basis van de content kunnen de frames grofweg in 4 groepen worden verdeeld: • Teksten: bijvoorbeeld de artiest of de titel van het nummer; • Commentaar/Lyrics: dat zijn natuurlijk ook teksten, maar het bijzondere is dat deze teksten een taalkenmerk hebben; • Plaatjes: bijvoorbeeld een afbeelding van de plaathoes; • Overige: een bonte collectie verschillende frames. Bij het opzetten van een Frame-class moeten we dus in ieder geval rekening houden met de mogelijkheid van verschillende eigenschappen en/of subclasses. De eerste opzet van onze FrameClass zou er nu zo uit kunnen zien: Type d
TV2Frame=class protected
FHeader : TV2Header; FContent: TBytes;
End;
14
MAGAZINE
Maar dat werkt niet echt lekker : de header van versie 2.0 (6 bytes) ziet er anders uit dan de header van versie 2.3/2.4 (10 bytes). Het volgende tabelletje geeft de opbouw van de frame-header in de verschillende versies
ID Size StatusFlag FormatFlag
Versie 2.0 3*AnsiChar 3 bytes -
versie 2.3/2.4 4*AnsiChar 4 bytes 1 byte 1 byte
Daarom kies ik ervoor de gegevens uit de frame-header bij het inlezen te vertalen naar de volgende properties. Het FrameID Een niet te negeren probleem is het verschil in ID. Een frame dat, als voorbeeld, de songtekst bevat wordt in versie 2.0 gedefinieerd als “TT2” en in 2.3/2.4 als “TIT2”. Om dat op te lossen heb ik in de toolkit een tabel opgenomen waarmee de conversie van versie 2.00 naar 2.3/2.4 kan worden gemaakt. Andersom zou ook kunnen maar lijkt niet zo zinvol. In die tabel is tevens informatie opgenomen over het type frame. Voor een deel blijkt dat overigens al uit de naamgeving. Zo zijn ID’s die beginnen met een “T” per definitie een TextFrame. Nou ja, natuurlijk is er een uitzondering, het frame “TXXX” is een “User defined text information frame”. Overigens mag een frameID alleen uit HOOFDletters en cijfers bestaan. Bij het inlezen wordt hierop getest. Als er een ongeldig FrameID wordt aangetroffen dan wordt aangenomen dat er een (lees)fout is gemaakt en wordt het lezen afgebroken. De Frame-Size De frame-size die we in de header vinden is in versie 2.3 en 2.4 bijna een gewone integer. Alleen staan de bytes niet in de volgorde die we gewend zijn. Die moeten we bij het inlezen even omdraaien. Hetzelfde is het geval bij versie 2.0 alleen is daar nog een extra complicatie omdat we maar 3 van de 4 bytes geleverd krijgen, de vierde moeten we er zelf even aanplakken. In de MP3toolkit is daar een kleine functie voor opgenomen. De Frame-vlaggen In een versie 2.3/2.4 header vinden we ook nog 2 vlaggen. De Statusvlag geeft informatie over de status van het frame. Zo is er bijvoorbeeld een vlag om aan te geven of een frame wel of niet gewijzigd mag worden (read only). Maar ook een vlag die aangeeft wat er met het frame moet gebeuren als de data wijzigt. Dat klinkt wat curieus maar het volgende voorbeeld maakt het wellicht begrijpelijker: in sommige bestanden kom je een “PRIV”, een “private frame”, tegen waarin het “peaklevel” is opgenomen. Een player kan die informatie gebruiken om het geluidsvolume te corrigeren. Dat frame is dus niet meer zinvol als we aan de audio gaan sleutelen. En dan is het natuurlijk handig als we dat aan een vlag kunnen zien. Maar op dit moment zijn we niet in de audio zelf geïnteresseerd dus negeren we de statusvlag gewoon. De Formatvlag is een ander geval. Hier vinden we onder andere informatie of er gebruik is gemaakt van encryptie en of compressie. Dat kan dus ook nog. Gelukkig ben ik het in de praktijk nog niet tegengekomen dus die formatvlag laat ik voorlopig ook met rust. Voor wie er in geïnteresseerd is, of het echt nodig heeft, kan de verdere documentatie over deze flags vinden op www.id3.org. ID3v2 Textframes De meest gebruikte frames zijn TextFrames. Zo bevat het frame TPE1 de naam van de artiest, TIT2 de songtitel, TALB de naam van het album, en TRCK het tracknummer. Zoals eerder aangegeven zijn (bijna) alle frames die beginnen met een “T” per definitie textframes.
DELPHI
En ook de frames die beginnen met een “W” zijn textframes, met dien verstande dat het de bedoeling is dat hier een webadres in staat. Zo is bijvoorbeeld WPUB de “Publishers official webpage”. En ook hier geldt weer een uitzondering voor “WXXX”. Bij het lezen (of beter gezegd: bij het schrijven) van textframes mogen er officieel maar 4 tekstcoderingen worden gebruikt: 0: Iso8859-1 1: UNICODE WITH BOM 2: UTF-16, without BOM 3: UTF-8 En voor wie echt oplet en denkt dat de twee codering op basis van de BOM (Byte Order Mask) eigenlijk in twee coderingen uiteenvalt heeft gelijk. Alleen is één van die twee coderingen dan toch weer gelijk aan UTF-16 dus houden we er toch weer 4 over. En ook hier kunnen we voor het lezen weer goed gebruik maken van het TEncoding type. Aangezien we met maar, maximaal, 4 coderingen te maken hebben genereren we die Encodings vast maar in onze framelist. Natuurlijk hebben we hier ook weer te maken met de eerder genoemde complicatie bij de ASCII codering. Officieel moet er in Iso8859 geschreven worden. Maar zoals eerder aangegeven is de praktijk dat iedereen schrijft volgens zijn eigen codepage. Dus komt ook hier onze beroemde Griek weer misvormd te voorschijn. En ook hier kunnen we dat eenvoudig oplossen door bij het lezen de standaard Iso8859 te overrulen door de op dat moment zinvolle Encoding. Eerder hebben we gezien dat we voor het converteren van data naar een string de volgende procedure gebruiken: MijnLabel := aEncoding.GetString(FData,aStartPos,aSize)
Over de Encoding hebben we het net gehad, de data hebben we gelezen, maar nu moet het begin en einde van de tekst nog worden bepaald. Voor een Textframe is dat eenvoudig: het eerste byte geeft de codering aan en je begint bij het tweede byte (met index=1). Wat betreft de lengte: dat is een kwestie van zoeken. Natuurlijk mogen we nooit verder lezen dan de data groot is, maar we moeten ook ophouden als we een nul karakter tegenkomen. En dat is nog even oppassen, want bij de coderingen 1 en 2 betekent dat dus totdat je twee nullen achter elkaar tegenkomt! Ik heb daar maar een tweetrapsraket van gemaakt. In de eerste trap wordt de lengte van de string bepaald, waarna in de tweede trap de string wordt opgehaald. En dat is alles wat we nodig hebben om textframes te lezen! Picture Frames In de ID3v2 tag kunnen ook frames met afbeeldingen worden opgenomen. Een voor de hand liggend voorbeeld is natuurlijk de afbeelding van de (plaat)hoes. De opbouw van een pictureframe is als volgt: Mime
in versie 2.0 drie ascii karakters, in 2.3/2.4 een zero-terminated ascii string
Picturetype Description Data
byte zero-terminated ascii string het feitelijke plaatje
Er zijn 21 voorgedefinieerde afbeeldingstypes, wat weer een functie voor onze toolkit oplevert. De Mime (Multipurpose Internet Mail Extensions) geeft het type afbeelding weer. Meestal hebben we te maken met “JPG” (versie 2.0)
en “image/jpeg” (versie 2.3/2.4). In het “description” veld zou een omschrijving kunnen staan, maar eerlijk gezegd heb ik nog nooit een voorbeeld gezien waar iemand de moeite heeft genomen daar wat in te zetten. Wat we nu bij het inlezen doen is deze gegevens decoderen en klaarzetten voor gebruik. Daartoe voegen we aan het TFrametype wat extra eigenschappen en functies toe. Een overweging zou zijn hiervoor een afgeleide TPicFrameType voor te maken. Maar daar heb ik vooralsnog van afgezien, en dit werkt prima! Het verwerken van een PictureFrame gaat als volgt: procedure TID3v2Frame.PreparePictureFrame (aVersion :byte); var lStartPos:TSizeType; begin if FData<>NIL then begin case aVersion of 2 : begin FMime := AsciiEncoding.GetString(FData,1,3); lStartPos:=4; end; else begin lStartPos:=1; FMime := AnyTextFromData(lStartPos, AsciiEncoding); end; end; self.FPicType := FData[lStartPos]; inc(lStartPos); self.Fdescription := AnyTextFromData(lStartPos, AsciiEncoding); FDataStart:=lStartPos; end; end;
Om de data van het plaatje op te halen maak gebruik ik van een TStream. Die kan dan direct aan een geschikte TGraphic gegeven worden om op scherm te tonen. Je moet dan overigens wel even aan de hand van de “Mime” controleren of het wel kan/mag! In het programma gebruik ik een TMemoryStream, maar je kan natuurlijk ook een TFileStream gebruiken om het plaatje op te slaan in een bestand. procedure TID3v2Frame.GetPicture(aStream: TStream); begin if FFrameType=v2fPIC then begin aStream.WriteBuffer(FData[FStartPicIDX], FDataSize - FStartPicIDX ); end; end;
Voor het schrijven gebruik ik ook weer een TStream. Je wordt geacht zelf het juiste mime-type mee te geven zodat daar geen problemen over kunnen ontstaan. procedure TID3v2Frame.SetPicture( aStream: TStream; aMime:String); begin setlength(FData, aStream.size); aStream.Seek(0, soFromBeginning); FDataSize :=aStream.Size; setLength(FData,FDataSize); aStream.Read(FData[0], FDataSize); FDataStart:=0; SetFrameIDStr(IDv2_PICTURE); FMime := aMime; end;
Bij het schrijven overschrijf ik de oorspronkelijke data. Die inhoud had ik toch al bij het inlezen verwerkt!
magazine voor software development 15
DELPHI
Commentaar frames Nog een speciaal geval. Er kunnen commentaarframes worden opgenomen. Deze hebben de volgende structuur: Tekstcodering Taal Omschrijving Commentaar
1 byte 3 bytes ascii zero terminated string string, (al dan niet zero terminated)
De taal mag iedere taal zijn, zolang die maar voorkomt in de standaardlijst met talen. Dat is dus weer een standaardfunctie in onze toolkit! Omschrijving en commentaar dienen te zijn geschreven in de aangegeven tekstcodering. En opgepast, bij type 1 wordt er twee keer een BOM (Byte Order Mask) geschreven! Het commentaar kan/mag uit meerdere regels bestaan. Overige frames En dan is er nog een bonte verzameling overige frames. Hiervoor verwijs ik graag naar de site www.id3.org [1]. Sommige zijn “user defined”, daar kan je dus niet heel veel mee. Voor een aantal anderen geldt dat er een specificatieveld is gedefinieerd. Voor een aantal frames heb ik dat in de broncode verwerkt. Een Schermvoorbeeld Voor het weergeven van de ID3v2 gegevens is uiteraard meer nodig dan voor de ID3v1 gegevens. Toch geef ik de basisgegevens op identieke wijze weer. Dit zijn toch degene die je het meeste gebruikt en dan kunnen ze maar overzichtelijk klaarstaan. Voor het voorbeeldbestand ziet het scherm er als volgt uit:
Rechts zien we het FrameID, met de omschrijving opgehaald in de MP3toolkit, links een deel van de inhoud. Merk op dat ook de standaardgegevens hier weer terugkomen. Die komen immers ook uit frames! De inhoud in de linkerkolom is in principe maar een gedeelte van de totale informatie in het frame. De rest van de informatie wordt ernaast getoond in een standaard TImage en TMemo component. Het schrijven de ID3v2 tag Om een lang verhaal kort te houden: dat is dus gewoon een kwestie van alle frames netjes achter elkaar plakken. Er zijn wel een paar dingen waar we rekening mee moeten houden… Als er helemaal geen informatie is dan worden we geacht geen ID3v2 tag te schrijven (een lege tag mag dus niet) en voor de meeste frames geldt dat er geen dubbelen voor mogen komen. Dus geen twee titels, maar ook geen twee uitvoerenden! De eerste controle is ingebouwd, de andere laat ik graag over aan een ieders fantasie (het probleem is dat je dat allemaal moet terugkoppelen naar de gebruiker, opties aanbieden wat te doen met het dubbele frame, enz.). Het zal dus niet meevallen om een echt waterdicht programma te schrijven. Standaard schrijf ik in versie ID3v2.3 (die komt immers het meest voor). Bovendien negeer ik bij het schrijven eventuele extended data (al is het maar dat ik dan niet het probleem heb dat de extended data mogelijk uit een andere versie komt). En volgens de definitie mag ik deze gegevens ook negeren! Verder kan ik bij het schrijven opgeven welke padding ik maximaal wil gebruiken. En niet te vergeten in welke codering er moet worden geschreven. Het standaard kiezen van een Unicode codering heeft als nadeel dat de meeste teksten twee maal zo groot worden zonder dat dit echt zinvol is. Dus misschien zou UTF-8 een goede optie kunnen zijn. Ik heb een iets flexibelere oplossing gekozen: je kan in het voorbeeldprogramma kiezen voor één van de volgende mogelijkheden: altijd UTF8 altijd Unicode Ascii waar mogelijk, anders UTF8 Ascii waar mogelijk, anders UNICODE
Fig. 4: De basisgegevens in de ID3v2 tag We zien nu alle talen gewoon door elkaar heen. Ik heb ook maar even aangegeven in welke codepage de gegevens in de ID3v1 tag zijn geschreven. (Een detail: De velden “Album” en “Commentaar” zijn geschreven in het arabisch en hebreews. Dus eigenlijk zou deze informatie rechts uitgelijnd moeten worden.) Onder deze standaardgegevens heb ik een tabel met alle gevonden frames opgenomen. Hieronder als voorbeeld een deel van de gegevens:
Bij de laatste twee opties wordt eerst gekeken of de tekst in de (standaard) ASCII codering goed wordt weergegeven. Zo ja, dan gebruiken we ASCII, zo niet de aangegeven multi-byte codering. Om te bepalen of er in ASCII kan worden geschreven kunnen we (alweer) gebruik maken van de TEncoding procedures. De truc is eigenlijk heel simpel: laat de encoding de string naar een TBytes vertalen, en zet die TBytes dan meteen weer terug naar een string. Als die hetzelfde is als waar we mee zijn begonnen dan kunnen we de Encoding gebuiken zonder dataverlies, anders schakelen we over naar de opgegeven multi-byte encoding: lBytes:=MyAsciiEncoding.GetBytes(aValue);
if MyAsciiEncoding.GetString(lBytes)<>aValue then GebruikMultiByte;
In de broncode is dit geïmplementeerd in de procedure GetAndSetTextType. Deze bepaalt eerst de te gebruiken codering en zet vervolgens de juiste code in de outputdata. Zoals we gezien hebben bij het lezen is dat het eerste byte van de data.
Fig. 5: Alle frames uit de ID3v2 tag
16
MAGAZINE
Het schrijven van de tags in een MP3-bestand Eerder heb ik aangegeven dat ik een TMP3file class gebruik waarin de tags als properties zijn opgenomen. Bij het schrijven wordt gekeken naar de EXISTS property van de beide tags om te bepalen of het geschreven moet worden. Als die op false staat, dan wordt een eventueel aanwezige tag automatisch verwijderd. Het vinkje op het scherm bepaalt hoe de property wordt gezet.
DELPHI
Bij het schrijven van ID3v2 tag, die vooraan het bestand geplaatst moet worden, kunnen er twee situaties ontstaan: • De nieuwe tag past precies in de ruimte van de bestaande tag • De nieuwe tag is groter of kleiner dan de bestaande tag. In het eerste geval kunnen we de nieuwe tag eenvoudigweg over de bestaande tag heen schrijven. In het tweede geval moeten we de rest van het bestand verschuiven, naar voren of naar achteren. Om dat te voorkomen kan een “padding” worden toegestaan. Dat wil zeggen een aantal “zero bytes” aan het einde van de tag. In de TMP3file heb ik een property “MaxPadding” opgenomen. Deze bepaalt de speelruimte waarmee geprobeerd kan worden om van situatie 2 toch een situatie 1 te maken. Standaard heb ik die op een waarde van 512 gezet. Je moet tenslotte iets kiezen. Maar goed, op een gegeven moment ontkom je er niet aan. In dat geval maak ik een tijdelijk bestand aan, schrijf daar de nieuwe ID3v2 tag in, en plak er de audio-data en eventueel de ID3v1-tag achter. En vervolgens kopieer ik dat bestand over het oude bestand heen. Ik gebruik daar een TMP3tempfile type voor dat zelf een unieke naam verzint in de standaard TEMP dir en die dat tijdelijke bestand ook weer voor me opruimt als ik klaar ben. Het aanmaken van dat tijdelijke bestand gaat dus wel lukken. Maar wat blijkt nu: overschrijven op een CD lukt niet, zelfs niet met beheersrechten! Het programma test er nog niet op, een verbeterpuntje dus! En dan even nog dit…. Een klein voorbeeldprogramma zegt meestal meer dan vele bladzijden tekst. Alle genoemde code, en meer, is terug te vinden in het bijbehorende zip-bestand, te downloaden van de SDN-site. Het staat een ieder vrij om die code te gebruiken en/of naar eigen inzicht aan te passen. Bij verdere verspreiding wordt bronvermelding op prijs gesteld. Uiteraard worden tips, aanvullingen, foutmeldingen enzovoorts ook op prijs gesteld. Maar voor u begint …. Het gebruik van het voorbeeldprogramma is “as is”. Aansprakelijkheid voor het verloren gaan van uw dierbare muziekbestanden kan niet worden aanvaard. Aangeraden wordt altijd eerst een backup van uw bestanden te maken voordat u ze aan gaat passen (met dit voorbeeld programma of met ieder ander programma). Met name als de audiodata moet worden verplaatst kunnen er allerlei buffer problemen ontstaan, zeker in een netwerksituatie (vraag maar liever niet hoe ik dat weet).
Maar laat u niet te bang maken. Die problemen zijn eerder incidenteel dan structureel. Het is alleen vervelend om je muziek kwijt te raken alleen omdat het netwerk even te druk is om zich met jouw bestandje bezig te houden. Daarnaast moet u er rekening mee houden dat iedere speler zijn eigen manier heeft hoe om te gaan met de informatie uit de tags. Gewoon een kwestie van proberen en zien wat er gebeurt. Zo accepteert mijn spelertje geen UTF8 teksten (het moet dan Unicode zijn). En daarnaast moeten de teksten ook nog met een nul afgesloten worden (wat volgens de standaard mag, maar niet hoeft). Het eerste kon ik nog wel uit de documentatie van de speler achterhalen, het laatste niet! En door het schrijven van dit artikel kwam ik er achter dat mijn speler wel Grieks en Russisch “spreekt” maar geen Arabische of Hebreeuwse teksten weer kan geven! Kortom het opschonen van je mp3 bestanden is maatwerk! Veel plezier! Links Bij het schrijven heb ik gebruik gemaakt van de volgende bronnen: • [1] www.id3.org : op deze website is alle informatie over de ID3 tags terug te vinden. • [2] het bestand MP3FileUtils.pas (v0.3a) van Daniel Gaussmann. Dit voorbeeld is te downloaden via www.gausi.de •
Peter van der Sman Peter van der Sman begon op de middelbare school met programmeren, in een tijd dat dit nog met ponskaarten gebeurde. De introductie van de PC maakte hij mee als chemisch analist en later als statisticus. Twee werkvelden waarin de computer een nuttig hulpmiddel is om de grote hoeveelheden (meet)data te verwerken. Midden jaren ’90 besloot hij, na zijn studie informatica, zich geheel te gaan wijden aan de automatisering. Via Prisman Consultancy houdt hij zich bezig met het maken van software en consultancy.
AGENDA 2008/2009
SDN Event, NBC – Nieuwegein .......................................................................................................................................................12 december SDN Magazine Nr. 100 .........................................................................................................................................................................................20 februari VO DevFest, London (UK) ...........................................................................................................................................................................12-15 maart SDN Event....................................................................................................................................................................................................................................27 maart SDN Magazine Nr. 101 .........................................................................................................................................................................................................8 mei Microsoft DevDays 2009 ...........................................................................................................................................................................................eind mei SDN Event ...........................................................................................................................................................................................................................................26 juni Genoemde data onder voorbehoud
magazine voor software development 17
Advertentie Logica
.NET
Marcel de Vries
Visual Studio Team System
"Rosario" Het zal de meeste ontwikkelaars niet zijn ontgaan: Visual Studio 2008 is gereleased. De Team System omgeving is in Visual Studio 2008 op een groot aantal punten verbeterd en je ziet dat het met deze omgeving steeds beter mogelijk wordt de softwarelifecycle onder controle te krijgen. Misschien ben je net aan de slag gegaan met Visual Studio 2008, of werk je nog gewoon met de Visual Studio 2005-omgeving, toch wil ik in dit artikel alvast een blik werpen op de toekomstige ontwikkelingen op het gebied van Visual Studio Team System (VSTS) en Team Foundation Server(TFS). Zelf ben ik van namelijk mening dat vooruitkijken erg belangrijk is voor het maken van de juiste keuzes vandaag. Door middel van dit artikel hoop ik je inzicht te geven in wat we kunnen verwachten van Microsoft als het gaat om het verder versterken van de tools die we nodig hebben om de steeds maar complexere applicaties goed te ondersteunen. De volgende versie van VSTS en TFS worden momenteel ontwikkeld onder de codenaam "Rosario". Voor "Rosario" heeft Microsoft een thema gedefinieerd waaraan alle nieuwe features worden opgehangen. Het thema is: "Bouw de juiste software op de juiste manier". Onder deze noemer zul je een aantal toevoegingen gaan zien aan de huidige toolset waarvan momenteel al een subset publiek beschikbaar is in de vorm van specificaties en een Community Technical Preview(CTP). In dit artikel baseer ik mij op de features die men al publiek heeft gemaakt en terug te vinden zijn in de april CTP.
Bouw de juiste software op de juiste manier Visual Studio Team Architect Edition Het kan misschien als een schok komen, maar zelf ben ik zeer blij met het feit dat Microsoft er voor heeft gekozen in Team Architect UML 2.0 diagrammen te gaan ondersteunen. Daarbij moet wel direct worden opgemerkt dat het niet het doel is van Microsoft volledig UML te gaan ondersteunen. Men heeft de keuze gemaakt modellen te kunnen gebruiken als ondersteuning voor het bouwen van software en daarbij gebruik te maken van de kennis die men al heeft op dit gebied. Om die reden heeft men er ook voor gekozen alleen de meest gebruikte diagrammen te ondersteunen. De diagrammen die je in ieder geval terug zult vinden zijn: het (Conceptual) Class diagram, het (Conceptual) Sequence Diagram, het Component Diagram en het Use Case Model. In de april CTP kun je al een eerste indruk krijgen van een aantal van
de diagrammen. Daarbij moet ik opmerken dat het Sequence Diagram er het meest compleet uitziet. Wat erg krachtig is aan de Sequence Diagrammen is dat men daarbij ook goed heeft nagedacht over het snel vol raken van een diagram waardoor je het overzicht dreigt te verliezen. Doordat je eenvoudig delen van de sequence kunt in- en uitklappen kun je heel snel overzicht krijgen. Tevens heeft men een optie toegevoegd om delen van een sequence te verplaatsen naar een ander diagram dat dan via een "click through" eenvoudig benaderbaar blijft. Op die manier is het prima mogelijk voor een architect zijn intenties duidelijk te maken, waarna de detailinvulling naderhand bijvoorbeeld wordt gecompleteerd door een ontwikkelaar. Er missen in de huidige implementatie van de diagrammen nog een aantal belangrijke features op zowel het UML-vlak als op het vlak van usabillity, maar het geeft al wel een aardig idee waar het uiteindelijk naartoe gaat. In de manier van gebruik van diagrammen binnen een project zijn er duidelijk twee stromingen te onderscheiden. De top down benadering waarbij de architect/ontwikkelaar begint met het maken van een ontwerp en de bottom up benadering waarbij er reeds sprake is van een bestaande codebase en men van daaruit diagrammen wil kunnen zien.
Team Architect in Rosario ondersteunt UML 2.0 diagrammen Om die reden heeft Microsoft het ook mogelijk gemaakt vanuit de bestaande code diagrammen te genereren die een afspiegeling zijn van het gemaakte systeem. Hiervoor heeft men een zogenaamde Architecture Explorer gemaakt. Met deze Explorer kun je bij een bestaand product makkelijker inzicht verkrijgen in o.a. de structuur van het product. Je kunt er b.v. eenvoudig de eerder beschreven sequence- of class-diagrammen reverse engineeren. In figuur 1 is een screenshot te zien van de Architecture Explorer. Hierbij zie je in één oogopslag de explorer (onderkant) en het sequence diagram dat is gegenereerd op basis van de broncode in de Team Foundation Server.
Fig. 1: Architecture Explorer en sequence diagram
magazine voor software development 19
.NET
Visual Studio Team Developer Edition Binnen de developer edition heeft men zich voornamelijk bezig gehouden met het probleem dat beter bekend is als het "Non Repro" probleem. Dit is het probleem waarbij een tester of eindgebruiker een fout constateert in het product maar dit vervolgens niet kan reproduceren in een ontwikkelomgeving. Het kost vervolgens zeer veel tijd en geld om het probleem boven tafel te krijgen. In sommige gevallen blijkt het zelfs echt onmogelijk om het probleem te reproduceren. Vaak is dit gerelateerd aan andere verschillen tussen productie- en ontwikkelomgeving en in steeds grotere mate gerelateerd aan concurrency problemen. Zeker met de komst van MultiCore-processoren zal dit probleem in de toekomst alleen maar groter worden. Om dit probleem het hoofd te gaan bieden introduceert Microsoft in de developer edition van Team System zogenaamde Historical Debugging. Historical debugging kun je het meest eenvoudig vergelijken met de Flight Data Recorder die in een vliegtuig zit. Deze recorder houdt de belangrijkste gegevens bij tijdens een vlucht en zodra er sprake is van een probleem (in het ergste geval zelfs een crash) dan zal alle benodigde data beschikbaar zijn voor analyse achteraf. Historical debugging doet feitelijk hetzelfde. Tijdens het testen van een applicatie kan men deze "flight recorder" op de achtergrond laten meedraaien. Gedurende het testen (of eventueel zelfs in productie) kan dan de tester bij het rapporteren van een bug de historische execution log aan het Bug-workitem koppelen. Met deze log kan vervolgens de ontwikkelaar direct zien welke codepaden zijn doorlopen. De integratie wordt zelfs zover doorgevoerd dat de debugger te switchen is tussen live mode en historical mode, waarbij in historical mode de ontwikkelaar door de code kan steppen alsof de applicatie live aan het werk is. Hierbij kan de ontwikkelaar alle bekende debug informatie zichtbaar maken, waaronder de waarden van variabelen die gebruikt zijn gedurende de uitvoering van het programma. Zaken als Exceptions, File IO, Registry Access, etc. worden ook allemaal vastgelegd. Je kunt je voorstellen dat dit natuurlijk een zeer grote vooruitgang is om het "No Repro" probleem het hoofd te bieden en dat je dan als ontwikkelaar niet eerst veel energie hoeft te steken in het reproduceren van het probleem voordat je het kunt opsporen. Je kunt dus bij het openen van een Bug direct de log laden, er door heen stappen en zien wat er mis is gegaan in de applicatie.
Historical Debugging werkt als een Flight Recorder Historical debugging is al een aantal jaren in ontwikkeling bij Microsoft onder de Code Naam "Protheus". Op dit moment heeft men het in de planning zitten de Protheus runtime, die op de achtergrond de traces bijhoudt, als losse executable beschikbaar te stellen zodat je deze als onderdeel van je applicatie kunt meeleveren. Daarmee kun je dan zelfs in een applicatie een optie inbouwen om tijdelijk de Protheus logging aan te zetten. Dan kan zeer waardevol zijn indien de klant een lastig te reproduceren probleem constateert. Door dan de logs te laten opsturen kunnen die lastig te vinden productieproblemen eenvoudiger worden opgelost. Naast historical debuggen heeft men ook gekeken naar het concept van Impact Analysis. Daarbij heeft men zich momenteel beperkt tot Impact Analysis op het vlak van uit te voeren unittesten ten gevolge van het aanpassen van code. Hierbij gaat het erom dat tijdens het aanbrengen van aanpassingen in de code de omgeving kan bepalen wat de minimaal uit te voeren set unittesten is om te valideren of een aanpassing regressie tot gevolg heeft gehad. Het idee daarbij is dat men vaak enkele tientallen en soms zelfs ettelijke honderden unittesten beschikbaar heeft, maar geen idee heeft welke testen zouden kunnen aantonen of een aanpassing mogelijk regressie tot gevolg heeft gehad. Met behulp van Test Impact Analysis wordt op basis van de
20
MAGAZINE
code-coverage-data uit de unit testen, die is opgeslagen op de server gedurende de builds, bepaald of een unittest uitgevoerd zou moeten worden. Zodra wordt geconstateerd dat een Unittest coverage heeft op de regels code die aangepast zijn, zal deze unit test als "aanbevolen" test worden aangemerkt. Je kunt dan vervolgens na afloop van het doorvoeren van je verandering deze aanbevolen lijst uitvoeren om te controleren of een van deze testen faalt. Deze feature is overigens niet alleen erg handig in de IDE, maar ook tijdens het uitvoeren van een Build, zeker in het geval je zogenaamde "Continious Integration" (CI) build hebt aangezet op een project. Daarbij is namelijk het doel zo snel mogelijk een build te maken die aantoont of een change mogelijk problemen veroorzaakt in de totale codebase. Door nu in een CI build alleen de "aanbevolen" testen uit te voeren kan een minimale set unit testen worden gebruikt voor het aantonen van regressie in de build. Dit stelt ons in staat om een CI build zeer efficiënt in te richten. Visual Studio Team Test Edition Een van de meest in het oog springende nieuwe features is, denk ik, de nieuwe test tools die beschikbaar komen. In de huidige (2008) Visual Studio Team Test editie is heel duidelijk de "technische" tester de doelgroep. In Rosario heeft men ook heel duidelijk de functioneel tester in het vizier. Dit maakt men mogelijk door de introductie van een nieuwe toolset, met als doelgroep functioneel tester, die buiten de Visual Studio IDE wordt gebruikt. Deze applicatie heeft de naam "Camano". "Camano" is een applicatie die ondersteunt in het opzetten, beheren, plannen en uitvoeren van testen. Hierbij introduceert Microsoft een aantal termen die overal in de tooling terug komen, te weten: Test Case, Test Suite, Test Configuration, Test Plan en Test Pass.
De Functioneel Tester wordt niet langer vergeten Een test case bevat de stappen om een specifiek onderdeel van een applicatie te testen. Een test case is een workitem dat o.a. de stappen bevat die in een test moeten worden uitgevoerd. Meerdere testcases worden samengevoegd in een test suite, voordat er een test kan worden uitgevoerd. Een test suite kan je in "Camano" samenstellen uit een aantal te selecteren test cases of door middel van een query. Dit laatste noemt men dan een dynamic test suite. Test configurations zijn aanduidingen van omgevingen waar een test wordt uitgevoerd, b.v. Windows Vista SP1 met FireFox browser, of Windows XP SP3 met IE7. Test suites kun je samen met een test configuration gaan inplannen om uit te voeren door de groep testers. Hierbij kun je specifieke testen toekennen aan testers en kun je het test plan activeren zodat de benodigde workitems op de naam van een betreffende tester worden gezet. Het uitvoeren van een test wordt een test pass genoemd. Op een test pass wordt gerapporteerd wat de resultaten van de test waren. De resultaten worden door de tester tijdens het uitvoeren van de test aangegeven in de test tool "MSRun". Deze test tool dient ter ondersteuning tijdens de testuitvoer en biedt een aantal opties (zie figuur 2). Fig. 2: MSRun test applicatie
.NET
De MSRun test tool kent een aantal opties, waaronder het op de achtergrond opnemen van de acties die zijn uitgevoerd, het vastleggen van de machine instellingen zoals CapsLock, NumLock, service packs, etc., het opnemen van een zogenaamde automation strip, etc. Al deze opties resulteren in losse logs die bij het vastleggen van een bug ook de omstandigheden duidelijk maken waaronder de fout is opgetreden. Er wordt zelfs een video-opname gemaakt van wat de tester allemaal heeft uitgevoerd. Bij het rapporteren van een bug worden al deze gegevens opgeslagen bij het workitem, zodat de ontwikkelaar direct kan zien waar het probleem zich heeft voorgedaan. In de toekomst komt daar ook de Protheus log bij, zodat de ontwikkelaar ook meteen door de historische data van de testrun kan stappen om zo het probleem zo snel mogelijk boven tafel te krijgen. Gedurende het uitvoeren van de testen is één van de opties het opnemen van een zogenaamde automation strip. Deze automation strip kan voor twee doeleinden worden ingezet: ten eerste voor de ondersteuning van een hertest en ten tweede voor het automatiseren van de test als een Coded UI Test. Voor de ondersteuning van de handmatige testen kan de automation strip worden ingezet voor het afspelen van stappen die in een voorgaande run ook al eens zijn uitgevoerd. Stel je bijvoorbeeld eens voor dat een testcase 25 stappen bevat en je alleen de laatste stap opnieuw moet controleren. Je kunt dan de test starten, de opgenomen test automation strip uit de vorige test pass gebruiken om automatisch de stappen tot en met stap 24 voor je te laten uitvoeren. Dit wordt gedaan door in het MSRun tool te kiezen voor de playback van de automation strip tot een aangegeven punt. Vervolgens hoeft de tester alleen de laatste stap nog zelf uit te voeren. Deze manier van werken zorgt er voor dat voor regressie testen de scenario's op exact dezelfde manier worden doorlopen en zorgt daarnaast voor een stukje werkgemak voor de tester. Zoals aangegeven, kun je de automation strip ook inzetten voor het verder automatiseren van test cases. Hiervoor kun je dan een test toevoegen aan een test project van het type "CodedUItest". Dit lijkt erg op een unit test, echter is dit specifiek bedoeld voor UI test automation. In deze test kun je de automation strip gebruiken om code te laten genereren die de stappen volledig automatisch voor je uitvoert. Je kunt dan tevens een zogenaamd verification point toevoegen, die bijvoorbeeld de waarden uit een control uitleest en kan vergelijken met verwachte waarden. Op deze manier kun je relatief eenvoudig de UI testing volledig automatiseren en de kosten van het regressietesten significant reduceren. De test tools zijn primair bedoeld voor de functioneel testers. Dit is ook direct herkenbaar doordat de tester niet Visual Studio hoeft te gebruiken voor het maken van de test cases, suites, configurations en plans. Ook de rapportage over een test pass wordt volledig in Camano gedaan en geeft een grafisch overzicht van de resultaten tot op dat moment voor een betreffend test plan. Camano zelf is volledig in WPF geschreven en geeft door middel van de Team Foundation Server de mogelijkheid op een juiste manier in het team samen te werken. De test cases, plans en results worden allemaal opgeslagen in Workitem Tracking en een aparte Team Test Database. Op die manier worden ook de resultaten t.a.v de historie keurig bijgehouden. In figuur 3 is een screenshot van Camano weergegeven, waarbij de resultaten van een test pass worden getoond. Team Foundation Server Als laatste wil ik nog heel kort kijken naar de Team Foundation Server. Ook aan die kant worden de nodige verbeteringen doorgevoerd. In bijvoorbeeld Version Control wordt er hard gewerkt om er voor te
Fig.3: Camano test pass details zorgen dat de historie van een change set beter inzichtelijk te maken is. Momenteel komt het nogal vaak voor dat je niet duidelijk kunt zien of een bepaalde change nu wel of niet onderdeel is geworden van een specifieke branch. Daarom maakt men een optie voor het visualiseren van de history van een change set. Op die manier wordt dan inzichtelijk in welke branch de change set op een gegeven moment in de tijd aanwezig is. Dit is zeer nuttig om bijvoorbeeld inzichtelijk te maken in welke versie van een product een bugfix terug te vinden is. In figuur 4 is weergegeven hoe deze visualisatie er uit zal komen te zien:
Fig. 4: Change set visualization Verder werkt men ook hard om zgn. conflict resolving beter voor ons op te lossen. Hierbij is het doel om zoveel mogelijk scenario's te automatiseren, zodat de tooling de juiste keuzes maakt en de ontwikkelaar dit alleen nog hoeft te controleren.
Beter inzicht in change set history … Als laatste wil ik ingaan op veranderingen die worden gemaakt t.a.v. de Team Build omgeving. In de 2008 versie is er al een aardige aanpassing geweest aan de architectuur van Team Build. Toch heeft men er voor gekozen om ook in "Rosario" een significante verandering in de aanpak te kiezen. Een van de veelgevraagde mogelijkheden is namelijk de optie om niet een eenmalig script te genereren voor de build, maar dat de build definitie continue aanpasbaar blijft vanuit de IDE. Daarbij wil men ook graag dat de build-stappen gevisualiseerd worden. Om die reden heeft het team ervoor gekozen de toekomstige team-build-omgeving wederom volledig opnieuw te bouwen. Deze keer doet men dit op basis van Windows Workflow Foundation (WF).
magazine voor software development 21
.NET
Door middel van WF kan men namelijk naast de eenvoudige buildvisualisatie (Workflow designers) ook een veel robuustere runtimebuild-omgeving realiseren. Deze heeft dan standaard ondersteuning voor zaken als parallel uitvoeren van stappen en het distribueren van stappen over machines heen. Last but not least, geeft dit ook de oplossing voor het aanpasbaar houden van de buildstappen door middel van een editor. In Team Build maakt men het mogelijk een build uit te laten voeren door een op dat moment vrije buildmachine uit een pool aan machines. Hierbij wordt gekeken of een buildserver uit de pool voldoet aan gestelde randvoorwaarden alvorens deze wordt gebruikt om de build te queuen. Het selecteren van een machine uit de pool wordt op basis van zogenaamde Tags gedaan. Door Tags aan een server te hangen en aan een build-definitie kan dan een beschikbare server gekozen worden uit de pool met machines. Conclusie Al met al kunnen we stellen dat Team System "Rosario" een significant aantal nieuwe features kent. De focus bij al deze features ligt in het feit dat ze ondersteuning moeten bieden aan het eerder genoemde thema. Door middel van het toevoegen van architectuurtools als UML en een architectuur explorer maakt men het mogelijk ook in een Microsoft omgeving eerst de concepten op te schrijven in diagrammen en vervolgens pas te starten met het implementeren. Met behulp van de nieuwe Team Developer features maakt men het mogelijk sneller fouten op te sporen, wat ons productiever maakt. Door middel van de nieuwe test tools kan beter worden gecontroleerd of alles wat we hebben gemaakt, in overeenstemming is met de afspraken die we hebben gemaakt. Door vervolgens al deze features naadloos op elkaar aan te sluiten maakt Microsoft het mogelijk applicaties steeds efficiënter te ontwikkelen en de kosten voor realisatie en onderhoud in de toekomst verder te reduceren.
Voor mijzelf is "Rosario" zeker een versie waar ik nu al naar uitkijk. Zodra de officiële bèta's beschikbaar komen weten we beter waar het product daadwerkelijk naartoe gaat en of de features die we tot nu toe hebben gezien, ook daadwerkelijk in het eindproduct terug te vinden zullen zijn •
Marcel de Vries Marcel de Vries is IT-Architect bij Info Support voor de Business unit Finance en MVP. Marcel heeft na vele jaren ervaring opgedaan met het .NET platform bij het bouwen van Enterprise administratieve applicaties voor grote bedrijven in Nederland. Naast het schrijven van artikelen voor SDN en Microsoft.NET magazine is hij een veel gevraagde spreker op seminars en conferenties waaronder Microfsoft Tech-Ed, Developer Days en SDC. Naast zijn werkzaamheden bij diverse klanten geeft Marcel trainingen bij Info Support in het .NET curriculum. Verder is hij bij Info Support architect binnen het innovatie team van Professional Development Center waar de Info Support software ontwikkelstraat wordt gemaakt en beheerd. Vanuit deze rol is hij mede verantwoordelijk voor het opdoen van kennis over de nieuwste technologieën zodat deze kunnen worden toegepast in de ontwikkelstraat en verder kan worden uitgedragen binnen en buiten het bedrijf.
Advertentie 4Dotnet b.v.
ARCHITECTURE
Bert Loedeman en Steef-Jan Wiggers
Zeven Windows Communication Foundation Best Practices Windows Communication Foundation (WCF) is een nieuwe technologie voor het bouwen van servicegeoriënteerde applicaties. Het kan onder andere gebruikt worden voor het opzetten van een Service Oriented Architecture (SOA), maar ook voor simpelere scenario’s van communicatie tussen twee applicaties op dezelfde machine tot processen op verschillende machines. In dit artikel beschrijven de auteurs een aantal ‘best practices’ en wat je moet overwegen wanneer je WCF gaat toepassen. WCF kan worden toegepast wanneer je twee verschillende stukken code met elkaar wilt laten communiceren. Daarbij kun je aan verschillende scenario’s denken. Een mogelijk scenario is interoperabiliteit, waarbij een servicegeoriënteerde applicatie gemaakt kan worden die over het internet communiceert met andere services, geschreven in een totaal andere technologie zoals Java. Een ander mogelijk scenario op bescheidener schaal kan zijn wanneer je twee klasses in dezelfde assembly in hetzelfde proces met elkaar wilt laten praten. Samengevat kun je WCF gebruiken voor elk nieuw stuk code waar je over de grens van processen heen wilt of wanneer je binnen hetzelfde proces wilt verbinden met ontkoppelde objecten (zoals services). Je kunt hierbij het bestaan van .NET remoting, ASP.NET Web Services en Enterprise Services vergeten en je richten op WCF voor alle mogelijke communicatiebehoeften. Voor het bouwen van WCF services is een aantal best practices van toepassing. Met toepassing van deze practices kan men effectieve, veilige en betrouwbare services bouwen. In dit artikel zullen zeven practices worden toegelicht: • Gelaagdheid • Instantiëring van aanroep • Omgaan met fouten • Kiezen van de juiste service host • Verantwoorde manier van callbacks • Gebruikmaken van onderhoudbare proxy code • Beveiliging
gespecificeerd hoe de service wordt blootgesteld in termen als address, binding en contract (het abc). De binding bijvoorbeeld bevat informatie over: • welk netwerkprotocol gebruikt gaat worden; • codering van het bericht; • eventuele beveiligingsmechanismes die worden toegepast en of gebruikt gemaakt wordt van betrouwbaarheid (reliability); • transacties of andere mogelijkheden die WCF ondersteunt. Ten slotte is een host nodig om de service te kunnen laten werken. De zogenaamde service host kan een .NET proces zijn dat zelf gehost wordt in bijvoorbeeld een Windows console applicatie, IIS, of Windows Activation Service (WAS) voor Vista en Windows Server 2008. Client Aan de kant van de client, de service-consument, zijn drie zaken nodig om met WCF een service aanroep te kunnen uitvoeren, waarbij het niet uitmaakt of de service met WCF of met een andere technologie is geïmplementeerd: 1. Een service-contract definitie die overeenkomt met wat aan de server kant gebruikt wordt; 2. Een data-contract definitie overeenkomstig met wat aan de server kant gebruikt wordt; 3. Een proxy om berichten te maken om naar de service te versturen en antwoordberichten te verwerken. Figuur 1 geeft de situatie weer tussen de service aan de server kant en de client als consument in twee scenario’s zoals eerder als voorbeeld aangegeven.
Voordat we ingaan op de best practices leggen we kort uit hoe communicatie binnen WCF werkt. Server Verbinden van twee stukken code met WCF vereist dat je eerst een aantal zaken regelt aan de server kant. Ten eerste heb je een servicecontract nodig. Deze definieert de operaties die je wilt aanbieden via de service, samen met de data die er doorheen loopt. Vervolgens zijn datacontracten nodig voor complexe types die door de service operaties heen gaan. Deze contracten bepalen de structuur van de verzonden data, zodat deze data kan worden geconsumeerd of geproduceerd door de client. Daaropvolgend heb je een service implementatie nodig. Hier bevindt zich functionele code, die de vraag beantwoordt van binnenkomende berichten en wat er mee moet gebeuren. Naast implementatie, service- en datacontract is een configuratie van de service vereist. Binnen de configuratie wordt
Fig. 1: Communicatie tussen service en client
magazine voor software development 23
ARCHITECTURE
Scheiding in lagen Gelaagdheid is een belangrijk principe binnen het ontwikkelen van software. Het scheiden van functionaliteit in presentatielaag, businesslaag, en data(toegangs)laag is al jaren bekend. Het opdelen in lagen levert scheiding op van verantwoordelijkheden, makkelijker te onderhouden code en betere schaalbaarheid bij fysieke scheiding van de lagen. In de datatoegangslaag, beter bekend als Data Access Layer (DAL), ligt de verantwoordelijkheid van de vertaling tussen de database en het applicatiedomein. Een dergelijke verantwoordelijkheid is ook nodig in een aparte service laag die zich richt op vertaling tussen applicatiedomein en de servicegeoriënteerde buitenwereld (zie figuur 2).
Gelaagdheid is een belangrijk principe binnen het ontwikkelen van software
om consumerende applicaties te schrijven, dan kun je alle .NET types gebruiken in je datacontract. Je zult er voor moeten zorgen dat dergelijke types wel gemarkeerd zijn als datacontracten of te serialiseren zijn. Eigenlijk sta je voor diverse uitdagingen wanneer je datasets door WCF laat passeren, wat je het beste kunt vermijden (behalve in zeer eenvoudige scenario’s). Als je datasets wilt toepassen in WCF, werk dan met getypeerde datasets waarbij je moet trachten zelf getypeerde datasets als parameters of terugkerende waarde te gebruiken. Zoals eerder beschreven bevat een simpel contract een type als: List. WCF is ontworpen om enumerable collecties 'plat te slaan' in arrays op de service boundary. In plaats van het verminderen van interoperabiliteit maakt dit je leven eenvoudiger wanneer je collecties vult en gebruikt in de service zelf en in de businesslagen. Kijk eens naar de datacontract definitie voor het CustomerProductType in listing 2. [DataContract()]
Fig. 2: Scheiding in lagen
public class ConsumerProduct {
Een servicelaag impliceert dat de servicedefinitie zich in een aparte klassenbibliotheek bevindt, welke wordt gehost in een host omgeving zoals IIS. Binnen de servicelaag worden aanroepen doorgezet naar de businesslaag om de gevraagde operatie te verwerken. WCF maakt het mogelijk je servicecontract en operatiecontract attributen direct in je serviceklasse te stoppen. Dit is echter iets wat je moet zien te voorkomen. Het is beter de interfacedefinitie, die duidelijk definieert wat je service boundary is, af te zonderen van de implementatie van de service. Voor een simpele service ziet je contractdefinitie eruit zoals in listing 1.
private int productID;
private string productName; private double unitPrice; [DataMember()]
public int ProductID {
}
public string ProductName {
}
Listing 1 Een belangrijk aspect van serviceoriëntatie is het verbergen van alle details van je implementatie achter een service boundary. Dit geldt eveneens voor de technologie die je gebruikt voor de implementatie. Daarbij is het niet wenselijk dat je aanneemt dat de applicatie een complex datamodel ondersteunt. Een deel van je service boundary definitie is de datacontractdefinitie voor (complexe) types, welke als operatieparameters kunnen worden doorgegeven aan of kunnen worden teruggestuurd door de service Voor maximale interoperabiliteit en aansluiting met principes van serviceoriëntatie zul je niet specifiek .NET types, zoals DataSets of excepties, moeten versturen buiten je service boundary. Je moet je houden aan simpele datastructuurobjecten zoals klassen met properties en achterliggende member-velden. Je kunt objecten laten passeren die geneste complexe types bevatten, zoals klant en order collecties. Je mag daarbij echter niet aannemen dat een klant objectgeoriënteerde constructies als interfaces of basisklassen ondersteunt ten behoeve van interoperabiliteit met webservices, al dan niet geïmplementeerd met een andere technologie.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
Als jeSOFTWARE WCF DEVELOPMENT alleen alsNETWORK nieuwe manier van remoting technologie wilt toepassen om twee stukken .NET code met elkaar te laten communiceren over een proces met de verwachting of eis voor andere SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
24 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
get { return productName; }
set { productName = value; }
[DataMember()]
public double UnitPrice {
{
}
set { productID = value; }
[DataMember()]
[ServiceContract()] public interface IProductService [OperationContract()] List GetProducts();
get { return productID; }
}
}
get { return unitPrice; }
set { unitPrice = value; }
Listing 2 Enkele instantie per aanroep Een best practice is een instantie per aanroep in te stellen als default. WCF ondersteunt drie instantiemogelijkheden voor services, namelijk: 1. Per-Call; 2. Per-Session; 3. Single. Per-Call houdt in dat een nieuwe instantie van een serviceklasse wordt gecreëerd voor elke aanroep die de client doet van een operatie. Deze instantie wordt opgeruimd wanneer deze afgerond is. Dit is de meest schaalbare en robuuste optie. Per-session betekent dat client de service-instantie gaande houdt op de server zolang de client aanroepen blijft doen aan de service. Hiermee kun je de state van een service vasthouden in membervariabelen van de service-instantie. Je bent daardoor in staat stateful conversatie tussen client-applicatie en server-object te doen. Dit heeft echter wel een aantal neveneffecten, waaronder geheugengebruik op de server, waarmee de schaalbaarheid wordt beïnvloed. Ten slotte stelt de Single(ton) mogelijkheid je in staat aanroepen aan de service van alle clients te routeren naar een enkele instantie van de service op de server. Hierdoor verkrijg je één enkel punt als zogenaamde 'poortwachter'. Deze mogelijkheid is slecht voor de
ARCHITECTURE
schaalbaarheid, omdat alle aanroepen in de singleton instantie geserialiseerd worden (één voor één).
Singleton routeert alle calls naar een enkele instantie van de service Single en Per-Session delen vergelijkbare neveneffecten, zoals schaalbaarheid. De beste optie voor ontwerp van services is Per-Call, omdat het de schoonste, veiligste en meest schaalbare optie is. De andere opties hebben alleen nut wanneer je extra mogelijkheden nodig hebt van deze specifieke opties, zoals een stateful mechanisme. De code in listing 3 declareert een Per-Call service. [ServiceBehavior(
InstanceContextMode=InstanceContextMode.PerCall)]
public class ProductService : IProductService {
}
// Code
Listing 3 Omgaan met fouten Afhandeling van fouten is altijd al een belangrijk onderwerp geweest in het maken van applicaties. Dit geldt ook voor het maken van services met behulp van WCF. Wanneer een niet afgehandelde fout een service boundary bereikt, zal WCF deze opvangen en een SOAP fout retourneren naar de aanroepende partij. Standaard is deze fout nietszeggend en geeft deze geen details over het precieze probleem en de oorzaak ervan. Op zich is hier niets mis mee: het komt overeen met de ontwerpprincipes van serviceoriëntatie. Standaard toon je alleen die informatie aan de consument van een service die je wilt tonen. Je wilt voorkomen dat details als stack-traces, die normaliter bij foutafhandeling voorkomen, worden getoond.
handelt alle fouten af en gooit een FaultException exceptie. Dit geldt in alle gevallen wanneer een service van een exceptie kan herstellen en volgende aanroepen zonder problemen kan afhandelen. De FaultException is een speciaal .NET type dat zich binnen de .NET call stack gedraagt als een normale exceptie. Deze wordt echter anders geïnterpreteerd door de WCF laag die de messaging verzorgt. Je kunt het beschouwen als een afgehandelde exceptie, die naar WCF wordt aangeleverd in plaats van een normale exceptie. Deze exception propageert zichzelf in WCF zonder interventie van de service. Je kunt gedetailleerde gegevens achterwege laten richting de client over de reden van het probleem door gebruik te maken van de Reason property van het FaultException type. Als een FaultException wordt opgevangen door WCF, zal deze worden verpakt als een SOAP fault message, maar binnen het channel zal er geen fout plaatsvinden. Dit betekent dat je de client kunt instrueren de opgetreden fout af te handelen en de service kunt blijven benaderen zonder een nieuwe connectie op te zetten. Er zijn veel verschillende scenario’s denkbaar voor het afhandelen van excepties in WCF alsmede voor de manier waarop je de foutafhandeling in de servicelaag wilt regelen. De volgende basisregels moet je altijd hanteren: • Vang alle niet afgehandelde excepties in WCF af en werp een FaultException op als je service in staat is te herstellen en volgende calls zonder problemen kan afhandelen (dit moet mogelijk zijn als je instantie per aanroep in stelt (Per-Call)); • Stuur geen exceptiedetails naar de client behalve voor debugging doeleinden tijdens ontwikkeling; • Stuur geen gedetailleerde informatie naar client gebruikmakend van de reason Property van de FaultException . Listing 4 laat zien hoe een servicemethode een exceptie afvangt. Daarbij wordt geen gedetailleerde informatie van het opgetreden probleem naar de client gestuurd middels de FaultException : List IProductService.GetProducts() {
De architectuur van WCF is dusdanig dat je deze in twee lagen kunt opsplitsen, namelijk de channellaag en servicemodellaag. De channellaag is verantwoordelijk voor de communicatie-infrastructuur en de servicemodellaag voor een objectgeoriënteerd en declaratief programmeermodel. Figuur 3 geeft beide WCF architectuurlagen weer.
try {
ProductManager prodMgr = new ProductManager(); return
ConsumerProductEntityTransformer. GetConsumerProducts(
prodMgr.GetProductDetailList());
}
catch (SqlException ex) {
throw new FaultException<string>(
“The service could not connect to the data store”, // detailinformatie, “Unknown error”
}
}
// reden);
Listing 4 Fig. 3: Architectuur van WCF De channellaag is volledig te configureren en uit te breiden. De binding is hierop van toepassing. Een niet afgehandelde exceptie zal in de channellaag resulteren in een fout, nog voordat de service zelf bereikt wordt. Dit houdt in dat je een volgende aanroep van de client niet door dezelfde proxy kunt laten verlopen. Er zal een nieuwe connectie naar de server moeten worden gemaakt. Dit heeft tot gevolg dat je een goede servicelaag zult moeten ontwerpen. Deze servicelaag
De generic type parameter T kan elk gewenste type zijn, maar in het kader van interoperabiliteit wil je het liefst een simpele datastructuur gemarkeerd als datacontract of als types die te serializeren zijn. Kies de juiste service host WCF kent drie opties voor hosten van een service. Men kan zelf de service hosten (draaien van je services in elke gewenste .NET applicatie), hosten binnen IIS of gebruik maken van Windows Activiation Services (WAS).
magazine voor software development 25
ARCHITECTURE
Zelf hosten binnen een applicatie (self-hosting) geeft je de meeste flexibiliteit, omdat je zelf de hosting-omgeving opzet. Dit betekent dat je volledig toegang hebt tot je hosting-omgeving en dat je de configuratie zelf kunt coderen. Ook kun je zaken zoals operatiemonitoring en andere controles aanhaken aan de service door middel van events. Het zelf hosten van de service legt echter ook de verantwoordelijkheid van procesmanagement en andere configuratiemogelijkheden bij de bouwer. Listing 5 beschrijft een dergelijk scenario van self-hosting: public partial class MyServiceHost : Servicebase {
ServiceHost productServiceHost = null; public MyServiceHost() {
}
InitializeComponent();
op de hoogte wilt brengen van verandering in data die gevolgen kan hebben voor de client. Om dit voor elkaar te krijgen moet je een callback-contract definiëren dat is gekoppeld (overeenkomt met) aan je servicecontract. De client hoeft het callback-contract niet publiek te maken aan de buitenwereld als een service, maar de service kan het gebruiken om een callback te doen naar de client nadat de initiële aanroep reeds is gedaan vanuit de client naar service. Listing 6 creëert een servicecontract-definitie met daaraan gekoppeld een callbackcontract. [ServiceBehavior(
InstanceContextMode=InstanceContextMode.PerCall)]
public class ProductService : IProductService {
List IProductService.GetProducts()
protected override void OnStrart(string args[]) {
#region IProductService Members {
productServiceHost = new
{
ServiceHost(typeof(ProductService));
}
productServiceHost.Open();
GetConsumerProducts(
}
if(productServiceHost != null)
{
}
WAS is de beste keuze om nieuwere platformen te ondersteunen WAS is een onderdeel van IIS 7 (Windows Vista en Windows Server 2008) en het geeft je een hosting-model waarbij je jouw services kunt blootstellen (aanbieden) met andere protocollen dan HTTP, zoals TCP, Named Pipes en MSMQ. WAS is altijd de beste keuze als je nieuwere platformen gaat ondersteunen. Als je services buiten je intranet gaat aanbieden, zul je over het algemeen HTTP gaan gebruiken, dus IIS is het beste voor hosten van externe services. Als WAS hosting geen optie is voor draaien van services intern op het intranet, dan kun je overwegen zelf de services te hosten. Dit heeft als voordeel dat andere (snellere en meer capabele) protocollen binnen je firewall kunt gebruiken zodat het je meer flexibiliteit geeft ten aanzien van configuratie van je eigen omgeving. FULL-COLOUR
Verantwoord gebruik van callbacks WCF bevat een eigenschap om een callback te doen naar een client om asynchroon data te routeren of als een vorm van eventsignalering. SOFTWARE DEVELOPMENT NETWORK Dit is handig wanneer je aan de client kenbaar wilt maken dat een lang lopende operatie klaar is in bijvoorbeeld je backend, of dat je een client 50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
26 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
throw new FaultException<string>(
“Service could not connect to the data store”,
Listing 5 Hosten in IIS stelt je in staat je services uit te rollen in IIS door simpel weg de DLL te plaatsen in de \BIN folder en de .SVC files als service adresseerbare endpoints te definiëren. Hiermee verkrijg je op kernelniveau request routering van IIS, de IIS management consoles voor configureren van de hosting-omgeving, de IIS mogelijkheid van starten en hergebruiken van werkprocessen, en nog veel meer. Het grote nadeel van hosten in IIS (tot en met IIS 6) is dat je beperkt bent tot HTTP gebaseerde bindings. In de toekomst vervalt deze beperking met IIS 7.0 en WAS.
prodMgr.GetProductDetailList());
catch (SqlException ex)
productServiceHost.Close();
}
ProductManager prodMgr = new ProductManager(); return ConsumerProductEntityTransformer.
protected override void OnStop() {
}
try
}
}
“Unknown error”);
#endregion
Listing 6 Als je van plan bent callbacks te gebruiken, is het een goede overweging het aan te bieden via een publish/subscribe API als onderdeel van het servicecontract. Voor uitvoeren van een callback zal de service de context van een binnenkomende aanroep moeten opvangen en vasthouden in het geheugen tot het moment dat een aanroep terug heeft plaatsgevonden naar de client. De client zal een object moeten aanmaken, dat een callback-interface implementeert om binnenkomende aanroepen van de service te kunnen ontvangen. De client zal vervolgens dit object en haar proxy levend moeten houden zolang callbacks verwacht kunnen worden. Dit is een sterk gekoppeld communicatiemechanisme tussen de client en de service (inclusief objectafhankelijkheden), dus is het een goed idee om de client de controle te verschaffen over wanneer de koppeling start en eindigt door middel van expliciete serviceaanroepen die aanvangen en eindigen bij een conversatie. Grootste limiterende factor van callbacks is het niet kunnen schalen en mogelijk niet werken in interoperabiliteit-scenario’s. Het probleem met schalen is gerelateerd aan het feit dat de service een referentie moet hebben naar de client die in het geheugen wordt vastgehouden om een callback te kunnen uitvoeren op de referentie. Listing 7 illustreert hoe je client-notificatie kunt opvangen, opslaan en veranderen door middel van een callback-referentie voor een service. class ProductServiceWithCallbacks : IProductService2 {
static List subscribers = new List();
ARCHITECTURE
#region IProductService2 Members
List IProductService2.GetProducts() { throw new NotImplementedException(); } void IProductService2.SubscribeProductChanges() { IProductServiceCallback client = OperationContext.Current. GetCallbackChannel();
}
lock (subscribers) { subscribers.Add(client); }
void IProductService2.UnsubscribeProductChanges() { IProductServiceCallback client = OperationContext.Current. GetCallbackChannel();
}
lock (subscribers) { subscribers.Remove(client); }
#endregion
}
static void NotifyClientsProductChanged (ConsumerProduct product) { lock (subscribers) { foreach (IProductServiceCallback client in subscribers) { client.ProductChanged(product); } } }
Listing 7
Een van de principes van serviceoriëntatie is dat je schema’s en contracten deelt, geen types je niet twee typedefinities voor hetzelfde maken. Het kan geen kwaad aan de clientzijde een referentie aan te leggen met een assembly die ook door de service wordt gebruikt om toegang te krijgen tot .NET typedefinities van service- en data-contracten. Je moet ervoor zorgen dat de service bruikbaar is voor de client als ze niet beide toegang hebben tot een gedeelde assembly; dat is beter dan het hergenereren van dergelijke types aan de clientzijde op basis van metadata. Om dit te doen definieer je de types in een aparte assembly van de service-implementatie, zodat zowel de client als service een referentie kunnen leggen zonder additionele koppeling. Als je dit doet, introduceer je meer afhankelijkheid tussen client en service, maar dan vanwege productiviteit en snelheid op gebied van ontwikkeling en onderhoud. Zoals eerder bij het stukje over het omgaan met fouten is beschreven, kan een niet afgehandelde fout in een channel ook een fout opleveren. Dit geldt voor de meeste bindings. Wanneer een fout wordt geleverd aan een WCF client, wordt er een FaultException gegenereerd als het niet als FaultException op de service zijde is geïntroduceerd. Dit is echter niet voor elke binding consequent zo geregeld. Je wilt dat je service- en client-code zijn ontkoppeld, onafhankelijk van je binding. Het enige veilige dat je aan de client kant kunt doen is uitgaan van het een worst-case scenario als de service een exceptie gooit. In deze situatie vermijd je dat de proxy opnieuw wordt gebruikt. Het weggooien of sluiten van de proxy kan resulteren in een volgende exceptie. Dit betekent dat je de calls naar een service in een try/catch blok moet zetten en de proxy-instantie moet vervangen met een nieuwe in het catch blok, zoals te zien is in listing 8. public class MyClient {
ProductServiceClient proxy =
new ProductServiceClient();
public Window1() {
}
InitializeComponent();
private void OnGetProducts( {
object sender, RoutedEventArgs e) try {
Je zult interoperabiliteitproblemen tegenkomen omdat je callbackmechanisme is gebaseerd op Microsoft WCF technologie, en die is niet standaard. Het is een vendor-specifieke standaard, maar bepaalde zaken zijn uitgedrukt binnen een SOAP-bericht dat geconsumeerd of gebruikt kan worden door andere technologieën. Dus als interoperabiliteit een deel van de wensen en eisen van de klant zijn, kun je callback beter vermijden. Een alternatief kan zijn om een polling-API op te zetten waar een client komt vragen om veranderingen op bepaalde tijdstippen, of je kunt een publish/subscribe middleman service opzetten als broker subscriptions en publicaties om koppeling tussen client en service te voorkomen zodat deze wel te schalen zijn.
Listing 8
Gebruik maken van onderhoudbare proxy code Een van de principes van serviceoriëntatie is dat je schema’s en contracten deelt, geen types. Het je zo min mogelijk afhankelijk maken van de client van de servicedefinitie is daarbij noodzakelijk. Geen types buiten de service en de client die geen deel uitmaken van de service boundary. Echter, wanneer je zowel de service als de client schrijft, wil
Beveiliging Tot slot moet beveiliging rond WCF niet vergeten worden. Beveiliging van je applicatie en dus ook van een service is belangrijk om oneigenlijk gebruik of het vrijkomen van gevoelige data te voorkomen. In dit artikel gaan we alleen in op de beveiliging van TCP en SOAP verkeer. Hierbij speelt een aantal WCF-bindings een rol, waarvan de
}
DataContext = proxy.GetProducts();
catch (Exception ex) {
}
}
}
proxy = new ProductServiceClient();
magazine voor software development 27
ARCHITECTURE
meest gebruikte zijn: NetTcpBinding (intranet applicaties), BasicHTTPBinding en WSHTTPBinding (internet applicaties). De beveiliging van een WCF service kan worden onderverdeeld in twee soorten beveiliging, namelijk het beschermen van berichten tegen ongewenste meelezers (transportbeveiliging en berichtbeveiliging) en het voorkomen dat berichten meer dan eens worden aangeboden (beveiliging tegen replay-attacks). De beveiliging van een WCF applicatie gebeurt altijd per binding. Beschikbare beveiligingsopties verschillen per binding; een overzicht hiervan is te vinden op de volgende locatie: http://msdn.microsoft.com/en-us/library/ms731092.aspx.
Als er een connectie moet worden gelegd met verouderde (legacy) clients, zoals clients die ASMX web services verwachten, dan is er geen andere optie dan te kiezen voor BasicHTTPBinding. Omdat de beveiligingsopties van de WS* serie (bekend van WSE 3.0) ontbreken, is dit de minst te beveiligen binding. Het is daarom nodig dat er goed wordt nagegaan of clients de WS* beveiligingsopties ondersteunen, zodat kan worden gekozen voor de WSHTTPBinding. Let er wel op dat de WSHTTPBinding standaard is geconfigureerd op berichtbeveiliging, terwijl verouderde clients met WS* ondersteuning in een aantal gevallen (veel niet-Microsoft clients) enkel transportbeveiliging ondersteunen.
Transportbeveiliging en berichtbeveiliging Beveiliging van dataverkeer is mogelijk door te kiezen voor transportbeveiliging, berichtbeveiliging of een combinatie van beide. Transportbeveiliging is in de meeste gevallen efficiënter dan berichtbeveiliging. Transportbeveiliging heeft te maken met het risico dat tussenstations voor het verkeer deze beveiliging kunnen verbreken. Berichtbeveiliging is minder efficiënt, maar biedt wel ‘end-to-end’ beveiliging. De keuze voor één van beide technieken is sterk verbonden met de omgeving waarin het dataverkeer plaats vindt.
Om een binding aan te maken met berichtbeveiliging, moet de sectie security worden aangemaakt met als modus Message, waarin een message-sectie moet worden aangemaakt. In listing10 is hiervan een voorbeeld uitgewerkt, waarbij ook het clientCredentialType, de manier waarop de client zich verifieert bij de service, is gewijzigd van de standaard modus Windows naar verificatie met een certificaat.
Intranet applicaties Bij applicaties die op een intranet worden geïnstalleerd, is het verstandig om te kiezen voor een TCP binding met transportbeveiliging. Ten eerste is binair transport over TCP significant sneller dan op SOAP gebaseerd transport over HTTP. Dit is onder andere te danken aan de mogelijkheid om zonder IIS een verbinding te kunnen opzetten. Bij een intranet applicatie is er veel controle over eventuele tussenstations in de verbinding, waardoor het risico op verbreking van de beveiliging zeer klein is. De NetTcpBinding voldoet standaard aan deze manier van datatransport. In listing 9 wordt een door middel van transportbeveiliging beveiligde NetTcpBinding uitgewerkt. Om een beveiliging uit te werken dient altijd een security-sectie onder de binding-sectie te worden aangemaakt, waarin de beveiligingsmodus wordt weergegeven. In deze sectie moet de gekozen beveiliging door een subsectie worden uitgewerkt.
<security mode=”Transport”<
Listing 9 Internet applicaties Applicaties die dataverkeer hebben dat over het internet wordt geleid, hebben weinig baat bij transportbeveiliging. Als een tussenstation de beveiliging wil verbreken, dan is daarop geen enkele controle. Het ligt daarom voor de hand om te kiezen voor berichtbeveiliging. Omdat het versturen van binaire data over het internet niet mogelijk is, ligt het voor de hand om te werken met SOAP over HTTP. Het hosten van een internet applicatie in IIS heeft als extra voordeel dat het beveiligingsmechanisme van IIS kan worden gebruikt. Ook is IIS sterk in het beheren van services en in het bieden van schaalbaarheid. Als data erg gevoelig is, is het raadzaam om naast berichtbeveiliging ook te kiezen voor transportbeveiliging. Hoewel transportbeveiliging over het internet geen sluitende beveiliging biedt, maakt het de job van een hacker een stuk minder aantrekkelijk. Houdt goed in de gaten dat transportbeveiliging voor dataverkeer over het internet nooit SOFTWARE DEVELOPMENT NETWORK berichtbeveiliging zal kunnen vervangen.
<wsHttpBinding>
<security mode=”Message”>
<message clientCredentialType=”Certificate” />
Listing 10 Beveiliging tegen replay-attacks Als een WCF-service op de juiste manier is beveiligd met transportbeveiliging of berichtbeveiliging, wordt veelal vergeten dat er een risico is dat een onderschept bericht meermalen wordt aangeboden aan de service. In veel gevallen zal dit niet direct heftige problemen opleveren, maar in de volgende situaties is het van groot belang om te zorgen voor een beveiliging tegen deze zogenaamde replay-attacks: • Een opnieuw aangeboden bericht veroorzaakt inconsistentie van data. Denk hierbij bijvoorbeeld aan het verwerken van betalingsgegevens; • Een bericht wordt gerouteerd via onbetrouwbare tussenstations. Dit is eigenlijk altijd het geval bij berichtenverkeer via het internet; • Verwerking van berichten is relatief gezien het meest intensieve proces van de applicatie. In deze situatie kan een hacker zeer eenvoudig zorgen voor een bottleneck binnen de service en zo een ‘denial of service’ realiseren. Uit bovenstaande situaties blijkt dat het niet nodig is interne applicaties, bijvoorbeeld op een intranet, te voorzien van beveiliging tegen replay-attacks. In vrijwel alle gevallen waarbij het internet in het spel verschijnt, is het raadzaam om je tegen replay-attacks te
Het is raadzaam om je tegen replayattacks te beveiligen beveiligen. Het principe achter de beveiliging tegen replay-attacks is vrij eenvoudig en verloopt zoals te zien is in figuur 4.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
28 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Fig. 4: Beveiliging tegen replay-attacks
ARCHITECTURE
De client-applicatie ondertekent het bericht met een unieke code. De service controleert het verzonden bericht aan de hand van deze unieke code. Dit vindt plaats door middel van een replay-cache waarin gedurende een bepaalde termijn binnengekomen codes worden opgeslagen. Wordt de code gevonden in de cache, dan wordt het bericht afgewezen, anders worden code en timestamp in de cache opgeslagen. Om in WCF gebruik te maken van beveiliging tegen replay-attacks, moet een custom binding worden aangemaakt. Hierin maak je een security-element aan. Vervolgens kan met een aantal properties de beveiliging worden geregeld. Beschikbare properties zijn: • detectReplays: deze boolean property moet aan worden gezet om de beveiliging te laten functioneren. Dit is de standaardwaarde; • maxClockSkew: geeft het tijdsverschil aan dat wordt getolereerd tussen client en server. Standaard staat deze property ingesteld op vijf minuten, maar de beveiliging wordt strenger als deze tijd wordt ingekort; • replayWindow: hiermee wordt ingesteld hoe lang een bericht wordt beschouwd als ‘gevaarlijk’ met betrekking tot replaying. Berichten die binnen deze tijdspan vallen worden gecontroleerd; • replayCacheSize: het aantal unieke codes dat door de applicatie tegelijkertijd kan worden bijgehouden. Als het aantal berichten verzonden binnen de replayWindow groter is dan de cachegrootte, worden berichten geweigerd tot de cache weer ruimte heeft voor nieuwe unieke codes. Standaard kan de cache 500.000 berichten herbergen. Een listing van het instellen van beveiliging tegen replay-attacks vanuit de configuratie is afgedrukt in listing 11. <customBinding<
<security>
Bert Loedeman en Steef-Jan Wiggers Bert Loedeman is een ervaren, MCPD gecertificeerde, .NET ontwikkelaar. Naast alles dat met .NET te maken heeft, heeft ook SQL Server zijn interesse. Op beide gebieden heeft hij gedurende de afgelopen 4 jaren veel kennis opgedaan door het meedenken en meebouwen aan grote projecten in o.a. retailautomatisering en de verzekeringswereld. De laatste tijd concentreert hij zich steeds meer op het gebied van architectuur en inpasbaarheid van nieuwe technologieën binnen het Microsoft platform. Complexiteit is hierbij niet zozeer een beperking als wel een uitdaging. Naast het genieten van zijn werk als techneut, probeert hij zich sterk in te zetten voor het delen van kennis, bijvoorbeeld door het geven van cursussen. Steef-Jan Wiggers is een ervaren MCSD.NET gecertificeerde software ontwikkelaar en gecertificeerde MCDBA (SQL Server 2000) database ontwikkelaar, die in de loop der jaren een brede technische kennis heeft opgebouwd op het gebied van architectuur, systeem- en database ontwikkeling op het Microsoft platform. De afgelopen jaren heeft hij zich voornamelijk gericht op architectuur, integratievraagstukken, analyse en ontwerp en heeft zich het ontwikkelen met o.a. BizTalk, MSMQ en IBM MQ eigen gemaakt. Momenteel vindt hij dan ook zijn uitdaging in het ontsluiten van back-end systemen met behulp van de bovengenoemde technologieën, het ontwerpen van software architecturen en analyse van complexe business vraagstukken.
replayCacheSize=”800000” maxClockSkew=”00:03:00”
replayWindow=”00:03:00” />
replayCacheSize=”800000 ” maxClockSkew=”00:03:00”
replayWindow=”00:03:00” />
<secureConversationBootstrap />
Listing 11 Tot slot Met deze best practices tonen we aan dat het niet extreem ingewikkeld is om WCF in een applicatie te integreren. Hopelijk is aan de hand van dit artikel duidelijk geworden welke voordelen WCF biedt ten opzichte van het huidige .NET 2.0 framework en hoe je deze technologie het best kunt toepassen. De beste manier om de inhoud van dit artikel te gebruiken is om de beschreven best practices te doorgronden en ze toe te passen in je persoonlijke 'speeltuin'. •
Delphi
TIP:
Delphi 2009 $STRINGCHECKS OFF In Delphi 2009 is een speciale compiler setting te vinden genaamd STRINGCHECKS, die default "aan" staat. Deze compiler setting zorgt ervoor dat er voor Unicode Strings code gegenereerd wordt die compatible is met C++(Builder), wat belangrijk is als je bijvoorbeeld componenten of packages maakt in Delphi die door C++Builder gebruikt moeten worden. Echter, in de meeste gevallen worden er "gewoon" toepassingen gemaakt in Delphi 2009, zonder dat die door C++Builder gebruikt zullen worden. En omdat de STRINGCHECKS compiler setting wat inefficientere code genereert, is het aan te raden om deze compiler setting op OFF te zetten. Hoe doen we dat? Goeie vraag. De optie is zelf niet te vinden in de "Compiling" pagina van de Project Options dialoog, maar we kunnen hem wel opgeven in de "Additional switches to pass to the compiler" in the Other Options sectie van de Compiling pagina. Hier kunnen we "--string-checks:off" neerzetten (zonder de quotes), maar je kan dit ook in je source code opnemen: {$STRINGCHECKS OFF} De code die je hiermee krijgt is iets efficienter dan met string checks.
magazine voor software development 29
.NET C#
Dennis Vroegop
C# Deep Dive Op de middelbare school zei mijn leraar Engels eens tegen mij: “Engels is een taal die ontzettend makkelijk te leren is als je het een beetje wilt spreken, maar verd*%# lastig om goed te leren spreken”. Het was een katholieke school, dus die *%# zei hij ook letterlijk zo. Deze uitspraak is me altijd bijgebleven. Ik denk dat dit een van de meest waardevolle lessen van de gehele middelbare school tijd is geweest. Deze uitspraak geldt namelijk niet alleen voor de taal Engels, maar voor heel veel dingen die we tegenkomen. Zeker in ons vakgebied zien we dit. Grote hoeveelheden ontwikkelaars kunnen zich een techniek heel snel eigen maken, maar als je doorvraagt of hun code goed bekijkt, merk je dat ze eigenlijk maar een klein stukje van die technieken beheersen. Om een techniek goed te beheersen moeten ze enorm veel tijd investeren; tijd die ze meestal domweg niet hebben. In de meeste gevallen is ‘goed genoeg’ ook echt ‘goed genoeg’.
In de meeste gevallen is ‘goed genoeg’ ook echt ‘goed genoeg’ Toch loont het de moeite om eens wat meer de diepte in te gaan om echt te weten wat er allemaal mogelijk is en hoe het allemaal precies werkt. Voor de taal C# geldt dit ook. De taal op zich is niet zo moeilijk. De constructies liggen voor de hand, er zijn niet al te veel keywords en je bent vrij snel in staat om aardige applicaties te schrijven. Maar om C# echt goed te beheersen zul je toch een langere tijd bezig moeten zijn met het uitzoeken van allerlei zaken. Laten we eens beginnen met de meest simpele kant van C#: de value en reference types.
Value types en reference types Laten we eens bij het begin beginnen. De basis van een object georiënteerde taal zoals C# is dat je de meeste tijd bezig bent met het definiëren van types. Je maakt een hele verzameling classes die je al dan niet instantieert. Deze classes bevatten alle logica voor je applicatie. Maar classes zijn niet de enige types die je kunt maken: we hebben ook nog de structs. Voor veel ontwikkelaars is het verschil tussen beiden niet helemaal duidelijk. Wanneer gebruik je nu wat? Om het verschil duidelijk te maken moeten we eerst kijken naar de manier waarop de runtime met deze twee types omgaat. Classes zijn reference types. Dit houdt in dat de variabele die je declareert een pointer bevat, of liever: een referentie, naar het stuk geheugen dat je object in beslag neemt. Een struct echter is een value type: de variabele bevat in dit geval geen pointer maar daadwerkelijk de waardes die je wilt opslaan. Dit heeft een aantal consequenties; zo staan reference types op een andere plek in het geheugen dan de value types. Er zijn in je applicatie twee soorten geheugen beschikbaar: de heap en de stack. Nu wil ik hier niet ingaan op het geheugenbeheer van de CLR (we hebben immers een managed omgeving die het geheugen voor ons beheert) maar toch is enig begrip van deze twee noodzakelijk wil je het verschil tussen de reference type en de value type kunnen bevatten.
De stack en de heap Het komt er op neer dat de stack gebruikt wordt voor de code die op **dit** moment uitgevoerd wordt, terwijl de heap meer bedoeld is voor opslag van data voor de wat langere termijn. De stack is een kleiner stuk geheugen. Meestal staat de stack in de L2 cache van de processor; dit is een stuk geheugen dat enorm snel te benaderen is door de processor maar dat niet al te groot is. De heap echter is te vinden in het ‘normale’ geheugen (optimalisatie door de compiler en runtime kunnen dit beïnvloeden, maar ga er maar even vanuit dat dit altijd zo is). Zoals je je kunt voorstellen is dit soort geheugen vele malen groter maar helaas ook vele malen trager te benaderen dan de stack. Neem als voorbeeld even de code in listing 1. public class MijnClass {
}
//… en ergens anders … public void MijnMethod() {
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
De stack wordt gebruikt voor de code die op **dit** moment uitgevoerd wordt SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
30 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
int x = 42;
MijnClass c = new MijnClass();
FULL-COLOUR
50 cyaan 100 geel
public int Getal;
}
c.Getal = x;
Listing 1
.NET C# We hebben hier een class, genaamd MijnClass en een method in een andere class met de naam MijnMethod. In MijnMethod declareren we een int x en zetten de waarde op 42. Deze x komt op de stack te staan. Immers, een variabele die we declareren en gebruiken in een method zal waarschijnlijk vaker gebruikt worden en moet snel te benaderen zijn. Daarnaast maken we een instance c van de class MijnClass. Nu gebeurt er iets bijzonders: op de stack komt een pointer te staan met daarin een verwijzing naar een stuk geheugen op de heap. In de heap komt de inhoud van het object te staan, in ons geval de variabele Getal (met als type int). Waarom gebeurt dit?
Een class is een reference type Fig. 2 Een class is een reference type. Dus we moeten de referentie naar de waardes (de int Getal) opslaan. Aangezien we dit object niet voor niets aanmaken, wordt de pointer naar de data in de stack opgeslagen. Op die manier kunnen we hem snel benaderen. De data zelf staat echter op de heap: classes kunnen groot worden en zoveel ruimte hebben we niet op de stack. Het geheugen ziet er nu dus als volgt uit:
en kan dus weg. Een ander voorbeeld: for(int i=0;i<42;i++) {..}. De i hier is alleen maar van belang binnen de for-lus. Daarna kan hij weg. Alles wat op de heap staat is bedoeld voor de garbage collector. Deze objecten worden alleen opgeruimd als de runtime dat nodig vindt. Meestal is dat als de beschikbare hoeveelheid geheugen te klein wordt. Dit is niet veel anders dan hoe de meeste mensen werken: we ruimen kasten pas uit als we geen ruimte meer hebben voor de nieuwe spullen die we hebben gekocht. Toch?
Een struct is een value type Een struct is een value type. Deze bevindt zich dus op de stack. Neem de volgende struct (listing 3) als voorbeeld. public struct PersoonData {
}
public int Leeftijd;
public bool IsVrouw;
Fig. 1 Listing 3 Zoals je ziet hebben we nu op de stack 2 elementen staan: de variabele x (met de waarde 42 op de stack) en een pointer (een reference) naar c, waarvan de data op de heap staat. Als we nu een kopie van beiden maken, zoals in listing 2, ziet het geheugen er uit als in figuur 2. public void MijnMethod() {
int x = 42;
MijnClass c = new MijnClass(); c.Getal = x; int y = x; }
MijnClass d = c;
Listing 2 Er is nu een tweede reference bijgekomen die echter naar hetzelfde stuk geheugen op de heap wijzigt. Dat houdt dus in dat als we c.Getal veranderen dat dat ook gebeurt bij d.Getal. Immers, die wijzen naar dezelfde integer op de heap. Als we echter X aanpassen heeft dat geen enkel effect op Y. Er is een kopie van de waarde gemaakt.
Als je een variabele van het type PersoonData aanmaakt, komen er op de stack twee variabelen te staan: de integer Leeftijd en de boolean IsVrouw. Als je een kopie maakt van de variabele, kopieert de CLR dus twee variabelen. Neem nu eens de volgende listing 4: public struct DommeStruct {
}
public Int64 a, b, c, d, e, f, g, h, i, j, k, l, m;
Listing 4 Deze neemt op de stack 13 maal 8 bytes (long is een Int64, dus 8 bytes), dus 104 bytes in beslag. Op zich is dat niet zo’n probleem, maar wat als je nu het volgende doet? DommeStruct x; x.a = 1;
x.b = 2;
DommeStruct y = x;
DommeStruct z = y;
Listing 5 Alles wat op de stack staat zal erg snel niet meer relevant zijn en dus opgeruimd worden. Een voorbeeld is een integer die je als variabele in je method declareert: na de method is deze int niet meer van belang
Iedere keer als je een kopie maakt van de variabele, maakt de runtime ruimte voor de struct en kopieert hij alle waardes. Als we nu z.a
magazine voor software development 31
.NET C# aanpassen, heeft dat geen enkel effect op x.a. Het is immers een kopie. Maar dit soort constructies kosten wel erg veel ruimte en ook tijd. De stack is immers bedoeld voor kleine hoeveelheden geheugen die snel weer weggegooid kunnen worden; de stack is geoptimaliseerd voor kleine hoeveelheden geheugen. Grotere stukken kunnen beter op de heap geplaatst worden, dat scheelt in performance.
De stelregel is dan ook dat je moet proberen een struct niet groter te laten worden dan 16 bytes De stelregel is dan ook dat je moet proberen een struct niet groter te laten worden dan 16 bytes. Als je meer dan dat nodig hebt, kun je er beter een class van maken. Daarnaast is er nog een regel (die vrijwel nooit opgevolgd wordt): structs zouden immutable moeten zijn. Dat wil zeggen dat als ze eenmaal een waarde gekregen hebben, deze niet meer gewijzigd mag worden. Wijzig je hem toch, dan moet de oude struct van de stack verwijderd worden en zal er een nieuwe struct (met de nieuwe waardes) op de stack geplaatst worden. Dit heeft tot gevolg dat structs veel simpeler kunnen zijn en dus ook veel sneller. Vergeet niet: structs moeten zoveel mogelijk geoptimaliseerd zijn voor snelheid en een kleine geheugen footprint!
verschil tussen structs en classes, of liever, tussen value types en reference types niet goed begrijpen. Let hier op als je structs in je code gebruikt. Nog even de regels voor structs samengevat: Een struct: • is kleiner dan of gelijk aan 16 bytes • is immutable • kan niet afgeleid worden (of afgeleid zijn van andere structs) • kan wel interfaces implementeren • kan properties bevatten • kan methods bevatten • de waardes in de struct worden gekopieerd als je de variabele kopieert • moeten snel zijn Als je aan één van bovenstaande punten niet kunt voldoen, maak dan een class aan in plaats van een struct. Maar als je een struct kunt gebruiken, doe het dan ook: het scheelt enorm in performance. Primitive Types Er zijn een aantal speciale value types beschikbaar in C#. Deze noemen we de primitive types. Je kent ze ongetwijfeld wel, het zijn de types die je iedere dag weer gebruikt. In onderstaande lijst staan ze genoemd.
Naam
Alias
Bereik
Even kijken of je het goed begrepen hebt. Neem de code in listing 6:
System.Boolean
bool
True of False
class Program
System.Byte
byte
0 .. 255
System.SByte
sbyte
-128 .. 127
System.Char
char
Een unicode karakter
System.Decimal
decimal
{
private static readonly SomeWeirdStruct sws = new SomeWeirdStruct();
static void Main(string[] args) {
Console.WriteLine( sws.TelOp() );
Console.WriteLine( sws.TelOp() );
}
}
Console.WriteLine( sws.TelOp() );
private int X;
public int TelOp() {
}
}
System.Double
double
-1,79769313486232e308 .. 1,79769313486232e308
System.Single
float
-3,402823e38 .. 3,402823e38
System.Int32
int
-2.147.483.648 .. 2.147.483.647
System.UInt32
uint
0 .. 4.294.967.295
System.Int64
long
-9.223.372.036.854.775.808 .. 9.223.372.036.854.775.807
public struct SomeWeirdStruct {
-79.228.162.514.264.337.593.543.950.335 .. 79.228.162.514.264.337.593.543.950.335
this.X = this.X + 1; return this.X;
Listing 6 Wat is het resultaat? Het zal je misschien verbazen, maar het resultaat is: 1, 1 en 1. Waarom gebeurt dit? Het antwoord is eigenlijk simpel: we hebben een sws gedefinieerd en die gebruiken we in de Main. Echter, als we deze struct benaderen, krijgen we een kopie van de sws variabele die bij Program hoort. Dus iedere keer krijgen we een nieuwe variabele die, je raadt het al, keurig X op 0 instantieert. Dus TelOp() geeft iedere keer 1 terug. Hadden we van SomeWeirdStruct nu een class gemaakt (en de naam SOFTWARE DEVELOPMENT NETWORK aangepast naar SomeWeirdClass) dan had er 1, 2, 3 gestaan. Dit soort effecten kan een enorm verwarrend zijn voor mensen die het
System.UInt64
ulong
0 .. 18.446.744.073.709.551.615
System.Int16
short
-32.768..32.767
System.UInt16
ushort
0 .. 65.535
Wellicht vraag je je nu af: ”En hoe zit het met string?” Goede vraag, het antwoord is: string is een alias voor System.String maar… dit is een reference type. Strings staan niet op de stack maar op de heap. Dus die horen niet in bovenstaand rijtje thuis. De aliassen zijn een makkelijker manier om met de primitive types om te gaan. Onderstaande twee regels doen precies hetzelfde; als je de gecompileerde code bekijkt in ILDasm zul je geen verschil zien. Het grote verschil zit hem echter in de leesbaarheid. System.Int32 getal1 = new System.Int32(); getal1 = 42;
int getal2 = 42;
Listing 7
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
32 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Zowel getal1 als getal2 worden beiden aangemaakt op de stack en krijgen beiden de waarde 42. Welke vind jij makkelijker om te lezen? Enums Enumerated types, of kortweg enums, zijn in het .Net framework
.NET C# enorm krachtige types. In talen als C++ kennen we enums ook, maar daar zijn ze niet meer dan een mooie, symbolische naam voor een getal. In C# zijn het echter object georiënteerde types, die volledige strongly typed zijn. Enums zijn afgeleid van System.Enum, welke op zijn beurt komt van System.ValueType. Enums zijn dus value types en worden op de stack geplaatst. De waardes in de enum zijn standaard van het type Int32. Bekijk de volgende enum eens: enum Geslacht {
Onbekend, Vrouw,
}
Man
Listing 8 De compiler behandelt dit alsof je de volgende pseudocode geschreven zou hebben: struct Geslacht : System.Enum {
public const Geslacht Onbekend = (Geslacht)0; public const Geslacht Vrouw = (Geslacht)1; public const Geslacht Man = (Geslacht)2;
}
public Int32 value__;
Listing 9 Dit kun je niet compileren: de compiler staat ons niet toe om iets af te leiden van System.Enum. Maar dit is wel wat er onder water gebeurt: er wordt een aantal constantes gedefinieerd van het type Int32, welke door middel van de variabele value__ opgevraagd kunnen worden. Uiteraard kun je je enums ook zelf waardes geven, of zelfs van een ander type gebruik laten maken. Bekijk de volgende code eens: enum LeeftijdCategorie : ushort {
Baby = 0,
Voorbeeld: [Flags]
enum KlantSoort {
Onbekend = 0, Nieuw = 1,
ContantBetalend = 2,
FaktuurBetalend = 4,
}
CreditCardBetalend = 8
Listing 11 We kunnen nu dus zeggen: KlantSoort soort = KlantSoort.Nieuw | KlantSoort.ContantBetalend. Verderop in het kassasysteem kunnen we dit controleren: KlantSoort soort = KlantSoort.Nieuw | KlantSoort.ContantBetalend;
// kijk hoe deze klant moet betalen
if ((soort & (KlantSoort.CreditCardBetalend | KlantSoort.FaktuurBetalend))!=0)
Console.WriteLine("op rekening"); else
Console.WriteLine("kontant" );
Listing 12 Door middel van een simpele & en | bewerking kunnen we kijken welke vlaggen gezet zijn, zodat we precies weten of deze klant op rekening mag betalen of dat hij direct contant af moet rekenen. Dus… Structs zijn lichtgewicht, snel en makkelijk. Classes zijn langzamer, groter maar veelzijdiger. Enums zijn speciale structs met bijzondere eigenschappen. Kies de juiste constructie voor je type en je zult merken dat je applicaties sneller en beter leesbaarder worden. C# is een enorm veelzijdige taal, maar je moet er wel moeite voor doen om alle facetten onder de knie te krijgen. Maar als je die tijd en moeite neemt, zul je merken dat je code er enorm door verbetert. •
Kleuter = 5, Puber = 12,
}
Volwassene = 18
Listing 10 LeeftijdCategorie is nu intern een lijst van constanten van het type ushort (oftewel System.UInt16) en bevat de 4 waardes die we opgegeven hebben. Let op: je kunt niet zomaar alles als type opgeven. Stel dat we LeeftijdCategorie definiëren als enum LeeftijdCategorie: System.UInt16, dan krijgen we een compilatiefout. We mogen alleen de types byte, sbyte, short, ushort, int, uint, long en ulong gebruiken, en dan zijn alleen maar de aliassen daarvan toegestaan. Nu is dat gelukkig in de praktijk wel voldoende, maar de eerste keer dat je die fout in je compiler tegenkomt, sta je wel vreemd te kijken. Immers, een System.UInt16 is precies gelijk aan ushort. De reden daarvoor is me niet duidelijk, het is een vereiste van de compiler om de alias te gebruiken voor de enums. We doen het dus maar gewoon zo.
Dennis Vroegop Dennis Vroegop is software architect bij Detrio, waar hij onder andere verantwoordelijk is voor het begeleiden van .NET projecten. Hij is een programmeur in hart en nieren en is een echt community mens. Momenteel is Dennis naast zijn normale werkzaamheden ook voorzitter van de Nederlandse .NET gebruikersgroep.
Bit-flags zijn een speciaal soort enums. Deze hebben waardes die een veelvoud zijn van 2: op die manier kun je ze combineren en uitlezen met de & (and) en | (or) operators.
magazine voor software development 33
DELPHI
Bob Swart
Delphi 2009 Taaluitbreidingen:
Generics en Anonymous Methods Delphi 2009 is op 25 augustus aangekondigd, en was vanaf maandag 8 september leverbaar (alhoewel mensen met een subscription minimaal een week en soms nog langer op hun upgrade moesten wachten). Wat betreft nieuwe features ging de meeste aandacht uit naar de Unicode ondersteuning, gevolgd door DataSnap, maar ook de andere taaluitbreidingen zoals Generics en Anonymous Methods zijn beslist de moeite waard. Probleem is een beetje dat de on-line help zoals gewoonlijk wat achterloopt, en dus zijn niet alle (on)mogelijkheden even goed gedocumenteerd. Maar ik hoop met dit artikel (en mijn sessie tijdens de afgelopen SDC) een beetje licht in de schemering te kunnen werpen.
De syntax van Generics is grotendeels vergelijkbaar met Delphi for .NET Generics Laten we beginnen met Generics, of Parameterized Types, zoals ze ook wel in Delphi worden genoemd. Deze vinden hun oorsprong eerlijk gezegd in .NET, en waren vorig jaar beloofd als nieuwe feature toen het niet haalbaar bleek om Generics al toe te voegen aan Delphi 2007 for Win32. De syntax is grotendeels vergelijkbaar met die van de Delphi for .NET syntax, met als uitzondering dat het .NET Framework een aantal zaken regelt die onder Win32 niet mogelijk zijn (in .NET kun je alles naar een object boxen, en bestaan er al standaard interfaces om values met elkaar te vergelijken). Een generic type definitie kan gebruikt worden om functionaliteit te presenteren waarbij je van te voren nog niet weet of je die voor integers, strings, floats, of de een-of-andere object class wilt laten SOFTWARE DEVELOPMENT NETWORK gebruiken. Als syntax worden de vishaken gebruikt met een type aanduiding ertussen (meestal een T, maar je kan er alles voor gebruiken,
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
34 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
behalve reserved keywords). Om een verzameling van elementen van het type T weer te geven, gerepresenteerd door een array van T, kunnen we het volgende schrijven: type
Verzameling = Array of T;
We kunnen variabelen direct van het type Verzameling declareren, waarbij we dan wel voor de T een daadwerkelijk type moeten aangeven (de instantie van het type parameter als het ware), bijvoorbeeld als volgt: type VerzamelingIntegers = Verzameling; var X: VerzamelingIntegers; Y: Verzameling; // kan natuurlijk ook
Behalve de type parameter T kunnen we ook constraints toevoegen, die daarmee aangeven dat we bij de instantiatie van het type aan bepaalde voorwaarden moeten voldoen. Een constraint is ofwel een class type waar de T van afgeleid moet zijn, of een interface die de T moet implementeren. Dus om een verzameling van elementen van een type T weer te geven waarbij T ten minste een TComponent moet zijn, kunnen we de volgende syntax gebruiken: type
Verzameling = Array of T;
Het type Verzameling zal nu niet meer compileren, maar Verzameling wel, en dat geldt ook voor de elementen die dan minstens van type TComponent moeten zijn. We kunnen behalve TComponent ook TPersistent gebruiken, maar het is niet mogelijk om TObject als constraint op te geven. In plaats daarvan moeten we dan het keyword class gebruiken: type
Verzameling = Array of T;
En dit zegt dat we een verzameling kunnen bouwen van elementen die class instanties zijn (en geen integer of strings bijvoorbeeld). Generic Stack Om eens een voorbeeld te geven van het gebruik van Generics is het noodzakelijk om ook functionaliteit toe te voegen. Alleen maar een
DELPHI
Array van een type T is niet zo zinvol, maar als je er ook mee kunt manipuleren (of onderling vergelijken, zoals ik aan het eind van dit artikel ga doen), dan is het plotseling een stuk handiger. Een voor de hand liggend voorbeeld is de Generic TStack, die als volgt gedefinieerd kan worden: type TStack = class protected FCount: Word; FStack: Array of AnyType;//needs to grow when needed public constructor Create(Capacity: Word = 42); destructor Destroy; override; function function function function end;
Count: Integer; Push(const item: AnyType): Word; Peek: AnyType; Pop: AnyType;
Omdat ik een array gebruik om de elementen van type T in op te slaan, moeten we zorgen dat de stack groot genoeg is (en blijft). Als eerste voorziening heb ik de constructor een default argument gegeven die de stack een opbergruimte van maximaal 42 elementen geeft (waar in het begin nog niks in zit natuurlijk, alleen de maximale ruimte is vast gereserveerd).
Als de elementen instanties van classes zijn, dan wordt hun destructor niet aangeroepen … dit kan dus tot een geheugenlek leiden als je de stack niet hebt leeggemaakt voor je hem weggooit De destructor roept de SetLength weer aan om het array weer op te ruimen. Let op: als de elementen instanties van classes zijn, dan wordt hun destructor niet aangeroepen; dit kan dus tot een geheugenlek leiden als je de stack niet hebt leeggemaakt voor je hem weggooit. De TStack is dus niet de eigenaar van de items die je erin stopt (dat kun je er wel van maken, maar dat moet je bijvoorbeeld aangeven dat de elementen minstens classes zijn via de constraint syntax, en dan in de destructor eerst Free aanroepen van alle elementen voordat je de SetLength op het array aanroept – maar dat laat ik over als oefening voor de lezer). constructor TStack.Create(Capacity: Word = 42); begin inherited Create; FCount := 0; SetLength(FStack, Capacity) end; destructor TStack.Destroy; begin SetLength(FStack,0); inherited end;
De Push methode moet eerst kijken of het interne array van de stack wel groot genoeg is om het nieuwe element op te slaan. Zo niet, dan moet SetLength opnieuw worden aangeroepen om het array te laten groeien. Hiertoe kunnen we SetLength weer opnieuw aanroepen (die
er tevens voor zorgt dat alle bestaande elementen uit het array meegenomen worden naar de nieuwe lengte). In de code vergroot ik het array met stapjes van 1 (door de nieuwe benodigde count mee te geven), maar het is wellicht slimmer om dit in iets grotere stappen te doen, afhankelijk van de behoefte aan extra opslagruimte van de stack. De implementatie van de Peek en Pop spreekt voor zich, neem ik aan. Merk op dat we hier steeds het AnyType gebruiken, waardoor de stack dus inderdaad van alles kan bevatten, van strings tot integers of TDataSets. function TStack.Count: Integer; begin Result := FCount end; function TStack.Push(const item: AnyType): Word; begin Inc(FCount); if FCount > Length(FStack) then SetLength(FStack, FCount); FStack[FCount-1] := item; Result := FCount end; function TStack.Peek: AnyType; begin Result := FStack[FCount-1] end; function TStack.Pop: AnyType; begin Result := Peek; Dec(FCount) end;
We kunnen b.v de TStack gebruiken voor een lijst van strings die we in omgekeerde volgorde weer willen printen, en wel als volgt: type TStringStack = TStack<string>; var S: TStringStack; begin S := TStringStack.Create; try S.Push('Delphi 2009'); S.Push('C++Builder 2009'); writeln(S.Pop); writeln(S.Pop); finally S.Free end; end.
Of we gebruiken hem als een stack van TPersistent classes, waarbij we bijvoorbeeld de Name en de ClassName maar ook de UnitName (ook een nieuwe property in Delphi 2009) kunnen laten zien: procedure TFormMain.ButtonClick(Sender: TObject); var CompStack: TStack; Comp: TComponent; begin CompStack := TStack.Create; try for Comp in Self do CompStack.Push(Comp);
magazine voor software development 35
Advertentie Microsoft
Advertentie Microsoft
DELPHI while CompStack.Count > 0 do begin Comp := CompStack.Pop as TComponent; ShowMessage(Comp.Name + ': ' + Comp.ClassName + end ' (' + Comp.UnitName + ')') finally CompStack.Free end end;
Voor dit voorbeeld is het maar goed dat de stack niet zijn elementen allemaal “free’t”, want anders zou het Form plotseling leeg zijn nadat we deze code hebben uitgevoerd (leuk voor een demo, maar niet leuk in praktijk). Overigens zijn er al veel Generic types te vinden in de unit Generics.Collections, dus moeten we opletten niet het wiel opnieuw uit te vinden. Generic Methods Behalve Generic Types maak ik zelf ook veel gebruik van Generic Methods. Het enige jammere is dat ik hier geen gewone lokale routines voor kan gebruiken, maar dat in de vorm van class methods moet doen. We kunnen hierbij de type parameter op twee plaatsen toevoegen: of bij de class of bij de method zelf. In de volgende code snippet staan beide alternatieven naast elkaar: type TGeneric = class class procedure Swap(var X,Y: AnyType); static; end; type TGeneric = class class procedure Swap(var X,Y: AnyType); static; end;
Als het maar om één method gaat is het verschil niet zo groot, maar als er meerdere methods worden toegevoegd zul je al snel merken dat het flexibeler is om de type parameter toe te voegen aan de method en niet aan de class. Zeker als er methods komen die wellicht meer dan één type parameter hebben. De implementatie van de Generic Swap methode zelf is redelijk eenvoudig: een derde variabele van type AnyType en klaar is kees: class procedure TGeneric.Swap(var X,Y:AnyType); var Z: AnyType; begin Z := X; X := Y; Y := Z end;
Bij het aanroepen hoeven we natuurlijk geen instantie van TGeneric aan te maken, maar kunnen we direct de class function Swap aanroepen, met daarbij een specificatie voor de type parameter en de juiste argumenten. Voor een tweetal integer variabelen kunnen we dit als volgt doen: var A,B: Integer; begin A := 42; SOFTWARE B := 17;DEVELOPMENT NETWORK TGeneric.Swap(A,B);
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
38 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Op dezelfde manier kunnen we andere class methods bouwen zoals een IFF (afhankelijk van een expressie krijg je de TrueValue of the FalseValue terug) of een ChooseDef (kies een item uit een lijst, maar als de index buiten het bereik van de lijst is krijg je een default waarde terug). type TGeneric = class class function IFF( const Expression: Boolean; TrueValue: AnyType; FalseValue: AnyType): AnyType; class function ChooseDef(index: Integer; const values: Array of AnyType; default: AnyType): AnyType; end;
Hier zien we een verschil met het .NET Framework, waar de default waarde van een type door het framework zelf wordt gegeven. Dat bestaat in Win32 niet, vandaar dat ik de default waarde van het type AnyType als extra argument aan de generic method heb toegevoegd, en we hem bij iedere aanroep moeten meegeven. Niet zo elegant als de .NET implementatie, maar het werkt net zo goed. De implementatie van beide generic methods is als volgt: class function TGeneric.IFF( const Expression: Boolean; TrueValue, FalseValue: AnyType): AnyType; begin if Expression then Result := TrueValue else Result := FalseValue end; class function TGeneric.ChooseDef( index: Integer; const values: array of AnyType; default: AnyType): AnyType; begin if (index >= Low(values)) and (index <= High(values)) then Result := values[index] else Result := default end;
Voor ik verder ga met een laatste Generic Method (om het grootste of kleinste element van een lijst van type AnyType te vinden) wil ik eerst even een uitstapje maken naar een noodzakelijk bouwsteen daarvoor: de Anonymous Methods. Anonymous Methods Een Anonymous method is een methode zonder naam, maar wel een stuk code binnen een procedure of function sectie. Zoiets als een stuk code dat je via copy-en-paste ergens hebt neergezet maar met een “wrapper” eromheen. Ze zijn zeker niet overal even goed toepasbaar, en leiden ook niet altijd tot leesbare code, maar in sommige situaties kunnen ze een hulpmiddel zijn om de expressiemogelijkheden van de taal Delphi te vergroten. Aan de hand van een eerste eenvoudig voorbeeld zal ik tot slot een tweetal nuttige toepassingen van Anonymous Methods laten zien. Een Anonymous Method geven we aan met de keywords “reference to procedure” (of function), inclusief de parameterlijst. Het invullen gaat dan in een blok code, waarbij er geen puntkomma komt tussen de header en de eerste begin. type TProc = reference to procedure(x: Integer); procedure call(const proc: TProc); begin proc(42); end;
DELPHI var TheAnonymousMethod: TProc; begin TheAnonymousMethod := procedure(a: Integer) begin Button1.Caption := IntToStr(a) end; // einde van de Anonymous Method call(TheAnonymousMethod) end;
De assignment van het stukje code aan de variabele “TheAnonymousMethod” ziet er op het eerste gezicht niet zo leesbaar uit, vooral niet als het stuk code lang(er) wordt. Er zijn echter wel voordelen te halen uit het gebruik van Anonymous Methods, zoals ik in de volgende voorbeelden zal laten zien.
Een van de plaatsen waar Anonymous Methods van waarde kunnen zijn, is binnen de synchronize methode van een TThread component Nuttige Anonymous Methods Een van de plaatsen waar Anonymous Methods van waarde kunnen zijn, is binnen de synchronize methode van een TThread component. Dit zou anders een extra methode kosten, inclusief velden voor de parameters van deze methode. Dit is o.a. in meer detail besproken in de weblog van Allen Bauer (zie http://blogs.codegear.com/abauer /2008/ 09/08/38868), waar hij spreekt over het thrddemo project in de C:\Documents and Settings\All Users\Documents\RAD Studio\6.0\Demos\ DelphiWin32\VCLWin32\Threads directory. Grappig genoeg is de daadwerkelijke demo niet aangepast volgens zijn suggesties, dus kunnen we dat zelf uitproberen. De oorspronkelijke code is terug te vinden in bovenstaande locatie, maar de aangepaste versie met Anonymous Methods zou er als volgt uit komen te zien: procedure TSortThread.VisualSwap(A, B, I, J: Integer); begin {$IFDEF ANONYMOUS} Synchronize(procedure begin with FBox do begin Canvas.Pen.Color := clBtnFace; PaintLine(Canvas, I, A); PaintLine(Canvas, J, B); Canvas.Pen.Color := clRed; PaintLine(Canvas, I, B); PaintLine(Canvas, J, A) end end) {$ELSE} FA := A; FB := B; FI := I; FJ := J; Synchronize(DoVisualSwap); {$ENDIF} end;
De {$IFDEF ANONYMOUS} kan gebruikt worden om te switchen tussen de oorspronkelijke code (die de extra methode DoVisualSwap aanroept) en de aanroep van de Anonymous Method die de parameters A, B, I en J direct ontvangt. Een tweede voorbeeld van een nuttig gebruik van Anonymous
Methods heb ik gevonden bij het maken van generic methods voor het bepalen van het grootste of kleinste element uit een lijst van items van een bepaald type. Omdat hier de vergelijking tussen twee elementen cruciaal is, kunnen we dit niet zomaar implementeren. Daar hebben we iets extra’s voor nodig, wat we o.a. terugvinden in de Generics.Defaults unit. Generics.Defaults Delphi 2009 bevat de units Generics.Defaults en Generics.Collections die al een hoop voorgedefinieerde nuttige voorbeelden van Generics (en Anonymous Methods) bevatten. Zo bevat Generics.Defaults de definitie van het IComparer interface dat we straks willen gebruiken, en ook een TComparison Anonymous Method kunnen we hier terugvinden: type IComparer = interface function Compare(const Left, Right: T): Integer; end; TComparison = reference to function( const Left, Right: T): Integer;
Deze Anonymous Method kan o.a. gebruikt worden in de aanroep van de Construct methode van de TComparer class, die het IComparer interface implementeert. TComparer = class(TInterfacedObject, IComparer) public class function Default: IComparer; class function Construct(const Comparison: TComparison): IComparer; function Compare(const Left, Right: T): Integer; virtual; abstract; end;
Daarnaast bevat de Generics.Defaults unit voorbeelden om de gelijkheid van generic values te bepalen, en een niet-reference counted IInterface implementatie in het type TSingletonImplementation, inclusief de implementaties van QueryInterface, _AddRef en _Release. Zie de source code voor details. Combinatie van Anonymous Methods en Generics Het laatste voorbeeld is er eentje dat ik zelf regelmatig gebruik: een generic method die de grootste – of kleinste – waarde uit een lijst van values van een bepaald type teruggeeft. Vergelijkbaar met de ChooseDef, maar dan inclusief een vergelijking zoals we die in het IComparer interface uit de Generics.Defaults unit zagen. De definitie van mijn functions Min en Max is als volgt: type TGeneric = class class function Min(const values: array of AnyType; const Comparer: IComparer): AnyType; class function Max(const values: array of AnyType; const Comparer: IComparer): AnyType; end;
Als eerste argument geef ik de lijst van values van type AnyType mee, en als tweede argument het IComparer interface van hetzelfde type. Hoe we aan deze IComparer komen is nog even niet van belang – dat wordt tijdens de aanroep geregeld (met een Anonymous Method). Wat we eerst moeten bekijken is de implementatie van de Min en Max functies, waarbij we de Compare functie van het IComparer interface kunnen gebruiken om steeds twee values van het AnyType te vergelijken met elkaar. De Compare geeft een positief getal terug als links groter is dan rechts, en anders een negatief getal. In feite kun je het
magazine voor software development 39
DELPHI
zien als “links min rechts” als het getallen zouden zijn. class function TGeneric.Max(const values: array of AnyType; const Comparer: IComparer): AnyType; var item: AnyType; begin if length(values) >= 1 then begin Result := values[Low(values)]; for item in values do if Comparer.Compare(item,Result) > 0 then Result := item end end; class function TGeneric.Min(const values: array of AnyType; const Comparer: IComparer): AnyType; var item: AnyType; begin if length(values) >= 1 then begin Result := values[Low(values)]; for item in values do if Comparer.Compare(item,Result) < 0 then Result := item end end;
ik “grootste” definieer als het component met de langste string die de nieuwe ToString functie teruggeeft. Hiermee geef ik meteen aan dat ik van aanroep tot aanroep de implementatie van de vergelijking tussen twee elementen kan aanpassen door een andere Anonymous Method te schrijven. Dit voorbeeld ziet er uiteindelijk als volgt uit, waarbij ik eerst een array van TComponents maak door de Self.Components van een TForm af te lopen: procedure TFormX.ButtonClick(Sender: TObject); var SelfComponents: array of TComponent; i: Integer; begin SetLength(SelfComponents, Self.ComponentCount); for i:=0 to Self.ComponentCount-1 do SelfComponents[i] := Self.Components[i]; ShowMessage( TGeneric.Max(SelfComponents, TComparer.Construct( function(const Left,Right: TComponent): integer begin Result := Length(Left.ToString) – Length(Right.ToString) end)).ToString ) end;
Tot nu toe is de code nog goed te lezen en niet al te vreemd gezien de generic methods die ik eerder liet zien. De slagroom op de taart krijgen we wanneer we de Min of Max willen aanroepen, bijvoorbeeld door het grootste getal uit een dynamisch array van integers op te leveren. Het eerste deel van de aanroep van Max zou er als volgt uit kunnen zien:
Met deze code in de OnClick van een TButton op een form krijg je als resultaat de inhoud van het component met de langste ToString waarde. Maar natuurlijk is het nu niet moeilijk meer om een eigen criterium te implementeren, of dit principe te gebruiken voor andere Generics en/of Anonymous Methods.
TGeneric.Max([1,2,4,8,16,32,64,42,36,13],
Conclusie In dit artikel heb ik laten zien hoe de nieuwe Delphi 2009 taalelementen voor Generic Types en Generic Methods alsmede Anonymous Methods in elkaar zitten en hoe we die zelf kunnen maken en gebruiken. Vooral de combinatie van Generics en Anonymous Methods levert fijne nieuwe mogelijkheden die voorheen niet aanwezig waren zonder dit allemaal als vele losse routines uit te schrijven. Wie nog vragen of opmerkingen heeft kan me altijd per e-mail bereiken of een bezoek brengen aan mijn nieuwe trainingsruimte in Helmond Brandevoort (zie www.eBob42.com voor details) •
Voor het tweede argument hebben we dan iets nodig dat het IComparer interface oplevert. En daarvoor moeten we even terugdenken aan de Generics.Defaults unit, met daarin de TComparer class met de Construct class function die het IComparer interface teruggeeft. Omdat de Construct method een class function is, kunnen we gewoon TComparer.Construct aanroepen om een IComparer terug te krijgen. Moeten we alleen nog het Comparison argument van de Construct meegeven, en dat is een TComparison oftewel een reference to function(const Left, Right: T): Integer; Om een lang verhaal kort te maken: bij de aanroep van Construct kunnen we dus meteen een Anonymous Method schrijven en die meegeven als argument, zoals in de volgende code te zien is: TGeneric.Max([1,2,4,8,16,32,64,42,36,13], TComparer.Construct( function(const Left,Right: integer): integer begin Result := Left-Right end))
De Anonymous Method doet in feite niets anders dan Rechts van Links aftrekken om zo het gewenste gedrag te implementeren dat binnen de Max functie verwacht wordt. Tijdens de aanroep van TGeneric.Max geef ik dus behalve het type en de lijst van elementen van dat type ook meteen een dynamische vergelijk-functie mee voor elementen van dat type (in de vorm van een Anonymous Method). En dat moet je misschien twee of drie keer lezen voor het duidelijk wordt, maar werkt wel perfect.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
Als variatie op dit thema wil ik nog een laatste voorbeeld geven waarbij ik van een lijst van componenten de grootste wil hebben, en SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
40 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Bob Swart Bob Swart is werkzaam in de IT sinds 1983 en heeft daarbij een voorliefde voor (Turbo) Pascal en Delphi. Bob spreekt regelmatig op (internationale) conferenties over Delphi en heeft honderden artikelen geschreven, alsmede zijn eigen Delphi cursusmateriaal voor Delphi trainingen en workshops. Behalve voor het geven van trainingen, is Bob ook beschikbaar voor consultancy, coaching, ontwerp- en bouwwerkzaamheden, of andere ondersteuning op het gebied van software ontwikkeling met Delphi – voor zowel Win32 als .NET. Sinds de zomer van 2007 is Bob ook reseller van CodeGear producten zoals Delphi en RAD Studio. Bob Swart Training & Consultancy is gevestigd in Helmond Brandevoort, en beschikt over een eigen trainingsruimte van ruim 42 vierkante meter, inclusief een testlab voor alle mogelijke toepassingen. De voorliefde van Bob voor Pascal en Delphi heeft hij ook tot uiting laten komen in de namen van zijn kinderen Erik Mark Pascal en Natasha Louise Delphine.
Advertentie Avanade
DOT NET NUKE
Nick Boumans
Portaltechnologie:
SharePoint of DotNetNuke? De keuze voor portaltechnologie is vaak snel gemaakt. De keuze of men gebruik gaat maken van Microsoft SharePoint of DotNetNuke (DNN) ligt vaak minder voor de hand. Is de keuze eenmaal gemaakt, dan twijfelt men later vaak of wellicht toch het andere product het voordeel van de twijfel had moeten krijgen. Of men kiest deels uit onwetendheid: men kent of SharePoint of DNN. Als SharePoint specialist heb ik recent kennis gemaakt met de mogelijkheden van DotNetNuke. De kennis die ik hierbij heb opgedaan is voor mij van cruciale waarde om keuzes in de toekomst beter te verantwoorden. In dit artikel zal een aantal overeenkomsten c.q. verschillen van beide producten in kaart worden gebracht. Doel hiervan is het verbreden van uw inzichten in deze producten die u kunnen helpen bij het beantwoorden van de veelgestelde vraag: SharePoint of DotNetNuke?
Een Portal is een Gateway waarmee leden toegang hebben tot bedrijfsinformatie SQL Server Database Beide producten maken gebruik van intuïtieve Microsoft technologie en beide gebruiken SQL Server als database. DotNetNuke kan overigens ook op een andere database werken, maar alleen SQL Server wordt standaard ondersteund. Tevens kunnen zowel SharePoint als DNN uitgebreid worden door gebruik te maken van .NET-development. Zowel SharePoint als DotNetNuke gebruiken dus SQL Server voor de opslag van data. Een belangrijk verschil is echter dat we de SharePoint contentdatabase enkel en alleen mogen benaderen via het SharePoint interface (een site) of via het objectmodel (door te programmeren). DotNetNuke heeft een datamodel dat eventueel direct benaderbaar is. Zo kan men vrij snel in DotNetNuke direct in de database een portal-url aanpassen. In SharePoint is dit “helaas” niet mogelijk. Tevens is het met beide producten mogelijk om door middel van maatwerk SOFTWARE gebruikDEVELOPMENT te maken van een andere database. Dit hoeft dan ook NETWORK niet per definitie SQL Server te zijn. Deze “extra” database kan echter niet gebruikt worden als vervanging voor de installatie database!
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
42 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Central Admin en Host Account SharePoint gebruikt de Central Administration voor Farm-level beheertaken. De “Central Admin” is een aparte webapplicatie die apart te benaderen is. DotnetNuke kent het “Host” -account waarmee extra privileges worden verkregen na het authenticatieproces. Inloggen kan echter direct op de portal.
Fig. 1: SharePoint Central Administration Toevoegen van Custom WebParts en Modules SharePoint kent een tool die Stsadm.exe heet. Met deze commandline tool is het o.a. mogelijk om custom WebParts te deployen en beschikbaar te maken op een website. Dergelijke deployment gebeurt doorgaans middels een Windows Solution Package (.WSP). Een solution package is een gecomprimeerd bestand met daarin de functionaliteiten. Een of meer XML bestanden beschrijven waar de bestanden uit de .WSP file geplaatst dienen te worden. Het maken en deployen van een solution file vereist redelijke SharePoint kennis. Deployment is voor veel developers en bedrijven dan ook een bottleneck. De solution package dient gedeployed te worden naar de webserver(s). Bij gebruik van stsadm.exe kan dit niet gedaan worden via de webbrowser. In een organisatie betekent dit vaak het inschakelen van een beheerder met voldoende rechten. Modules voor DotNetNuke kunnen via de webbrowser worden toegevoegd. Voor het bouwen hiervan is uiteraard DNN-kennis vereist. Het packagen van een module (.zip-file) vraagt over het algemeen echter minder expertise dan het maken van een Solution Package voor SharePoint. Het nadeel van DotNetNuke ten opzichte van SharePoint op dit vlak is, dat DNN-modules alleen binnen DNN gebruikt kunnen worden. SharePoint WebParts kunnen gebruik maken van twee base classes: SharePoint WebPart of ASP.NET WebPart. WebParts gebaseerd op de ASP.NET base class kunnen ook in ASP.NET-sites worden gebruikt. SharePoint specifieke WebParts kunnen gebruikt worden in systemen die gebruik maken van Windows SharePoint Services. Hierbij kan men
DOT NET NUKE bijvoorbeeld denken aan Project Server. Een ASP.NET WebPart is weliswaar niet direct in DotNetNuke te gebruiken, het is wel mogelijk een module te maken waarin een WebPart wordt “gehost”.
Fig. 2: DNN - Toevoegen van een module Visibility In SharePoint kan men gebruik maken van Audience Targeting en Security Features om bepaalde data zichtbaar te maken of juist te verbergen. Vaak komt het voor (vooral in WSS) dat de gebruiker meer te zien krijgt dan hij of zij eigenlijk mag zien. Bij DNN is het mogelijk om instanties van modules (dus een module die op een pagina is geplaatst) te koppelen aan één of meer rollen. Per rol kan worden aangegeven welke rechten (View, Edit, …) van toepassing zijn op de betreffende module. Standaard worden de rechten op een module geërfd van de pagina, zodat gebruikers die de pagina mogen zien, ook de module mogen zien. Daarnaast kunnen aan gebruikers individuele rechten worden toegekend. Deze rechten komen dan wel altijd bovenop de rechten die de gebruiker vanuit de aan hem toegekende rollen heeft. Scalability DNN kan geïnstalleerd worden op één webserver en één SQL Server. Wanneer men DotNetNuke in een webfarm of –garden zouden willen laten draaien, dient men daarvoor een en ander handmatig te configureren. Concreet heeft iedere DNN-installatie dus één enkele “DNN-database”. Wel is het mogelijk DNN meerdere malen te installeren op een server. Dit in tegenstelling tot SharePoint. SharePoint is echt een serverproduct waarvan maar één instantie op een server geïnstalleerd kan worden. Het beste kan men SharePoint dan ook zien als een serverproduct en DNN als een ASP.NET applicatie. In SharePoint is het mogelijk de scalability te beheren vanuit de Central Administration. Het is zelfs mogelijk om bijvoorbeeld per Site Collection een aparte contentdatabase te hebben. Tevens kent SharePoint Features als Enterprise Search en Single Sign-on. We kunnen zelfs gebruik maken van een aparte Search en Indexing server. Hierbij kan men gebruik maken van uitgebreide indexeeropties. DotNetNuke stelt hier tegenover dat veel performance gerelateerde instellingen via het Host-menu kunnen worden geconfigureerd en dat standaard onderdelen zoals de zoek- en indexeermogelijkheden met maatwerk of software van derden kunnen worden aangepast. Office-Integratie en Multi-Language SharePoint is heel sterk in Office integratie (MOSS 2007: Microsoft Office SharePoint Server 2007). Bij DotNetNuke ontbreekt dit eigenlijk. Zo is het met Infopath Forms Services mogelijk om in een handomdraai een professioneel formulier te maken wat ingevuld kan worden door de gebruiker. De data kan opgeslagen worden in o.a. een SharePoint lijst. Een ander voorbeeld is integratie met Outlook. Bij gebruik maken van een workflow kan een taak gecreëerd worden in een takenlijst. Deze taak kan zichtbaar worden in de Outlook-takenlijkst van de gebruiker.
manier. Bij SharePoint is dit niet standaard. Het is uiteraard wel mogelijk om meerdere talen te installeren. Doorlooptijd en kosten Over het algemeen is de tijdsduur voor het up-and-running krijgen van een out-of-the-box DotNetNuke portal (installatie en configuratie) korter dan die van SharePoint. Dit geldt niet alleen voor interne hosting maar ook voor externe hosting. Wil men bij SharePoint eveneens toegang hebben tot de stsadm.exe tool dan zal men eveneens remote desktop toegang moeten hebben tot de webserver. Aangezien DotNetNuke Open Source is, en een zeer liberaal licentiemodel kent, vallen de kosten van DotNetNuke al snel lager uit. Sharepoint heeft ook de WSS variant die gratis verkrijgbaar is bij Windows 2003, maar deze is enigszins beperkt in functionaliteit ten opzichte van de grote broer MOSS 2007. Het is echter te kort door de bocht om te stellen dat DotNetNuke altijd goedkoper is. Deze afweging kan alleen per situatie apart worden gemaakt. Intranet v.s. extranet Met beide pakketten is het mogelijk om zowel een intranet- als internetportal op te zetten. Men kan hierbij gebruik maken van verschillende authenticatiemechanismen. Of de oplossing een internet- of intranetapplicatie moet worden kan een belangrijke factor zijn bij de bepaling van de keuze voor DotNetNuke of SharePoint. Praktijk: WebPart development vs. Module development In de volgende paragrafen zal dieper ingegaan worden op het development aspect van een WebPart c.q. Module. Hierbij is het nadrukkelijk niet de bedoeling om een complete tutorial te beschrijven voor de development hiervan, maar wil ik alleen enkele verschillen of overeenkomsten schetsen. Tevens dient opgemerkt te worden dat zowel bij WebPart- als Module development meerdere wegen bestaan die naar Rome leiden.
WebParts en Modules voegen functionaliteit toe om te voorzien in de bedrijfsbehoefte SharePoint: WebPart Development De eenvoudigste manier om een WebPart te ontwikkelen is door gebruik te maken van “WSS extensions for Visual Studio”. Deze extensies voor Visual Studio zijn zowel voor Visual Studio 2005 als 2008 beschikbaar als download op de site van Microsoft. Na het installeren van deze extensies hebben we binnen Visual Studio de beschikbaarheid over een aantal extra project templates. Een hiervan is het SharePoint WebPart.
Hier staat tegenover dat Multi-Language standaard is bij DotNetNuke. Dit is al sinds begin 2005 geïmplementeerd op de standaard ASP.NET
SharePoint is heel sterk in Office integratie; hier staat tegenover dat MultiLanguage standaard is bij DotNetNuke
Fig. 3: De beschikbare templates in VS2008 na installatie van WSS Extensions for Visual Studio
magazine voor software development 43
DOT NET NUKE Na de keuze voor “WebPart” ziet ons Visual Studio project er als volgt uit: Bij de referenties in het project kan men zien dat een referentie is toegevoegd naar de Microsoft.SharePoint.dll. Dit is slechts een van de vele dll’s die gebruikt kunnen worden voor SharePoint development. SharePoint objecten beginnen met “SP” (SharePoint). Een lijst heet dus binnen SharePoint een SPList, dit in vergelijking met een generic list. Door de toevoeging van SP voor een objectnaam zijn objecten voor .NET developers eenvoudiger te herkennen. Door een naamconventiefout zorgen de SPSite- en SPWeb objecten echter vaak voor verwarring: • SPSite geeft toegang tot de top-level site (dit is een collection) • SPWeb geeft toegang tot de pagina (hier zou men wellicht SPSite verwachten) Binnen een webpart kan men op de volgende manier toegang krijgen tot de pagina (SPWeb): SPSite siteCollection = SPContext.Current.Site; SPWeb site = SPContext.Current.Web;
Listing 1: Het benaderen van een webpagina (SPWeb) vanuit een WebPart Door in de Project Properties de juiste url op te geven bij “Start Browser With Url” kan met één druk op F5 gedeployed worden naar een development server. Als het goed is, is het WebPart nu beschikbaar in de WebPart Gallery en klaar voor gebruik. Het maken van een uitgebreider Solution Package en deployment binnen bijvoorbeeld een OTAP omgeving vergt uiteraard meer kennis en kunde dan enkel en alleen een druk op F5. Debuggen is mogelijk door de debugger te attachen aan het juiste W3WP proces. Naast de WSS Extensions gebruiken developers vaak tools van CodePlex om WebParts en Windows Solution Files te maken. Veel gebruikte tools zijn: STSDEV, WSPBuilder en SharePoint Solution Installer. DotNetNuke: Module Development Om een DotNetNuke Module te ontwikkelen kan men als vergelijkbare stap met de WSS Extensions for Visual Studio gebruik maken van de starterkit van DotNetNuke. Deze bevat eveneens een aantal project templates. Hiermee is een ontwikkelaar snel op het juiste spoor gezet. Aangezien DotNetNuke “gewoon” een ASP.NET applicatie is, kan het maatwerk worden gedebugged vanuit Visual Studio en zijn er op de ontwikkelmachine geen extra installaties nodig. Met DotNetNuke kan een ontwikkelaar al aan de slag met louter de (gratis) Express-edition producten van Microsoft. Een belangrijke keuze bij het maken is of men een module wenst te maken die gecompileerde bestanden bevat of een dynamic module. Bij een dynamic module worden de bronbestanden als het ware gecompileerd op de server: dat laten we dan over aan ASP.NET. In tegenstelling tot een WebPart die meestal als Windows Solution File gedeployed zal worden, is de module file een .zip-bestand. Vanuit onze DNN development omgeving kunnen we deze .zip-file laten genereren inclusief een configuratiefile. Hierbij is er de keuze voor het maken van een module met of zonder bronbestanden. Het gecrëeerde zipSOFTWARE DEVELOPMENT NETWORK bestand kan vervolgens direct toegevoegd worden aan een DotNetNuke site.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
44 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Fig. 5: Bij DotNetNuke worden diverse projecttemplates meegeleverd in de starterkit Conclusie Aangezien iedere oplossing andere requirements heeft, zal steeds overwogen moeten worden voor welk product we kiezen. Veel mensen neigen bij bijvoorbeeld een webshop meer naar DotNetNuke, terwijl bij een enterprise intranet, waarbij Office-integratie een grote rol speelt, SharePoint de voorkeur verdient. Scalability kan een belangrijk kwaliteitsaspect zijn. Door de huidige situatie te bekijken waar de applicatie aan dient te voldoen en wat de verwachting is over 5 jaar (bijv. omvang gebruikers- en site-toename) kan men een goed beeld krijgen van de vereiste scalability. Hierbij dient men te voorkomen dat de huidige keuze DNN is en over 5 jaar SharePoint (of vice versa). Een valkuil die men vaak ziet is dat een definitieve keuze gemaakt is voor DotNetNuke of SharePoint binnen een organisatie en dat men met dit product “alle” problemen probeert op te lossen. Heel geforceerd tracht men oude applicaties in dit product te verweven. Uiteraard kan het allemaal wel, maar men moet zich wellicht de vraag stellen: “is dit de beste oplossing voor deze situatie?”. Links: • Blog Nick Boumans: www.SharePointDevelopment.nl • WSS Extensions for Visual Studio: www.microsoft.com/ downloads/details.aspx?FamilyID=7bf65b28-06e2-4e87-9bad086e32185e68&displaylang=en • Microsoft Office SharePoint Server 2007 VHD: http://www. microsoft.com/downloads/details.aspx?FamilyID=67f93dcbada8-4db5-a47b-df17e14b2c74&DisplayLang=en • www.codeplex.com/sharepointinstaller • www.codeplex.com/wspbuilder • www.codeplex.com/stsdev •
Nick Boumans Nick Boumans is MOSS 2007 Technology Specialist en Enterprise Application Developer bij Giraffe IT.
CORE SYSTEMS
Rolf Craenen
Core Systems Wat hebben je hypotheek, een pinautomaat, de logistiek in de Rotterdamse Haven en (sorry) ook je belastingaangifte met elkaar gemeen? Ze draaien allemaal op kernsystemen in bewezen technologieën die vaak al tientallen jaren in de lucht zijn en bekend staan om hun performance en stabiliteit. In programmeertalen zoals COBOL, Uniface, NonStop, PL1 en C zijn applicaties ontwikkeld die bedrijfskritische processen ondersteunen van grote bedrijven en overheidsorganisaties. Het SDN gaat aandacht besteden aan software development van dit type applicaties in een nieuwe track met als naam Core Systems. Hiermee verbreden we de dekking van het SDN op het gebied van systeemkarakteristieken, gebruikte programmeertalen, databases en ontwikkelhulpmiddelen en hun leveranciers. Als je kijkt naar de karakteristieken van core systems, zie je dat er naast grote overlap met andere vormen van software development ook duidelijk verschillen zijn. Aan de ene kant zijn performance en stabiliteit sterke punten maar tegelijkertijd zijn de systemen minder flexibel en zijn developers schaars. Ook blijkt uit onderzoek dat een mainframe een factor 10 efficiënter met energie omgaat dan gedistribueerde systemen. Dit gegeven was recent voor bijvoorbeeld voor Duitse Postbank een reden om 3 mainframes aan te schaffen vanwege de aansluiting met hun groene it-beleid.
Uiteraard worden Core Systems ontwikkeld met software die al sinds jaar en dag hun plek hebben binnen het SDN. Met name door de wens om gegevens, reeds beschikbaar binnen de Core Systems, te ontsluiten richting internet. Tegelijkertijd kom je technieken tegen als Cobol, C, NonStop, IDMS, PL/1, DB2, CICS, TSO en ISPF. Bedrijven zoals IBM, HP en Compuware zijn al decennia belangrijke spelers op dit gebied. Binnen deze bedrijven bestaat veel enthousiasme om ook hun kijk op het Engineering-vakmanschap te delen binnen het SDN. Elders vind je het eerste artikel met als titel ‘The best of both worlds, Modernizing Core Sytems’. Op het SDN event van 12 december verzorgen wij 5 presentaties. Wij nodigen je van harte uit om kennis te komen maken. Heb je ideeën, wil je meedoen of meer weten, stuur dan een email aan [email protected]. Rolf Craenen •
Advertentie Qurius
GENERAL
Interesting Things:
Increasing speed Early October I presented a talk at the inspiring Software Development Conference 2008 in Noordwijkerhout, the Netherlands. My talk had an inspiring, or at least intriguing title: “How to keep our jobs”. Why? Well, let me share some thoughts on the productivity of software development projects. A 100-fold Recent research by Gartner shows that the productivity of software development will need to increase by a 100-fold. According to Gartner, there are 2 very simple and straightforward reasons for this: • Not enough newbies. The number of university graduates, and other people starting new with software development is too limited. • Increasing demand. The demand for new software is ever increasing, for instance because of emerging technologies and platforms (think of mobile and web 2.0). And from my personal experience I would add: • Poor quality software. I perform a lot of code audits on applications, often quite large, let’s say over 1.000.000 lines of code. Even with such large applications, built by respectable companies, the quality is often brain damaging poor, frequently due to failing or total lack of software architecture. Such software is therefore very hard to maintain or extend with new features. • Ongoing business. There is a vast amount of software present in the marketplace that will need to be maintained for a very long time. Large transactional systems with banks (nationalized or not), governmental organizations or packaged software such as SAP and PeopleSoft. Although not mind-blowing, this work still needs to be done, and limits the amount of resources available for new development. • No silver bullet. There is no single methodology, technology or company that is able to provide the productivity to meet up the Gartner challenge, no matter how hard vendors will try to convince you that their new tool or framework will get you there. • Complexity of technology. Technology is ever more increasing in complexity. As much as you might suggest that tools and technologies have evolved over the past 20 years - about as long as I have been in this business - we do not reach higher productivity in projects than we did back then during the client-server era. In good old PowerBuilder projects we reached productivities of around 2.5 hours per function point. Nowadays, when applying our full blown agile Accelerated Delivery Platform we can about get to the same level, as was recently calculated by a respectable customer. And this is 20 years later. Another quick example? Take service oriented architecture. Often presented by vendors and architects as an approach where you just model the business processes and out comes your new software. Yeah right. Building service oriented software is still hard and technologically complex work. And no matter who gave you the idea that service orientation will help you get rid of object orientation, don't believe it. You will need good old object technology to build all this stuff architects come up with: user interfaces, process logic, business logic, interfaces to middleware. It's just not that simple. • Band wagons. We developers tend to go along with every new tool, framework or language extension that comes of the Microsoft, Sun, SOFTWARE DEVELOPMENT NETWORK IBM, Oracle or open source band wagon and base our software architectures on these new, unproven technologies. This results in
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
46 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
much time being spent on solving technology issues, rather than customer issues. And I should know, I have been (re)building frameworks over the past 20 years. • Snail speed projects. Projects are often executed at snail speed, and have enormous amounts of slack-time wasted by people just waiting for other people to finish their work or waiting on decisions not being made. My worst case? I once spent 4 months lingering in a project that was on hold until a board high up in the organization indeed, a large international bank - approved the new budget. And I wasn't the only one - there were 60 of us. The sun always shines As said, there is no technique or technology that will lift our productivity a 100-fold. No sir. Moreover, there is not even a combination of technologies that will do so, no matter how much vendors would like you to believe that they've just invented the silver bullet. We're just not there yet. But we could try. There are promising technologies, tools and methodologies. There always have been. Some ideas will help us get underway slowly and steadily (alas): • Become agile. Letting go of large process and waterfall styled methodologies will produce better software at higher velocities. Agile software development is more cooperative, responsive to changes, and removes much of the air from waterfall styled projects. Not only in small, but also in very large and even distributed projects. After having evangelized agile software development for the past 10 years, I know see agile finally moving into the mainstream. • Become communicative. Do not design and write your software under water, and submerge after months. Keep in touch with your customer during the whole project. Preferably execute (at least a large part) of your project at the customers' site. Let him or her be in the lead. Shorten your feedback cycles to a daily, if not hourly basis. • Become standardized. Why does every new project define its own software architecture? And why are a large number of these new architectures so poorly designed? Why do we, developers, go along with every new (and often unfinished) technology fad? Standardization on good, sound architecture (domain driven of course) will help create better quality software that is easier to maintain and extend in the future. But be pragmatic, travel light. Even better, standardized software architectures allow for advanced, productivity increasing techniques, such as model driven software development and further down the road domain specific languages. Again, none of these ideas are the silver bullet to solve all our problems. And the current financial crisis doesn't help either. But, enough said. I'm not into pessimistic locking. We geeks have the best jobs in the world. The sun always shines from our asses. Just think of it. What other job will just let us write code all day? Sander Hoogendoorn blog.sanderhoogendoorn.org •
INFORMATION
WORKER UX
Donald Hessing
Silverlight biedt Nieuwe Mogelijkheden voor SharePoint Met de komst van Silverlight 2 is het ontwikkelen Rich Internet Applicaties (RIA’s) een stuk eenvoudiger geworden. Naast de bekende animatie- en video-mogelijkheden biedt Silverlight 2 ook een aantal rijke controls die het gemakkelijker maken om SharePoint content op een dynamische manier te presenteren. Adobe timmert al jaren aan de weg met interactieve content op het web. De inmiddels ingeburgerde Flash-plugin is op bijna elke computer te vinden. Dat ook Adobe gelooft in Rich Internet Applicaties blijkt uit het in februari gelanceerde Flex 3. Daar waar Flash meer bedoeld is voor grafische doeleinden, is Flex meer bedoeld voor het ontwikkelen van RIA’s. Ook Google timmert hard aan de Rich Internet weg met applicaties als GMail en Google Docs. Daar waar Adobe en Microsoft hun pijlen richten op een plugin voor de browser, ligt de focus bij Google op de open source-technologie Ajax. De technologie is voornamelijk gebaseerd op XML en Javascript, en juist om die reden heeft Google zijn nieuwe browser Chrome geoptimaliseerd voor Javascript.
Het feit dat controls belangrijk zijn voor de adoptie van Silverlight bij ontwikkelaars is ook bij Microsoft geland Silverlight De eerste versie van Microsoft Silverlight is volledig gebaseerd op Javascript en is mede door het beperkte aantal controls geschikter voor het maken van leuke animaties dan voor het bouwen van RIA’s. De nieuwe versie, Silverlight 2, is afgelopen oktober uitgebracht en bevat een rijkere controlset dan Silverlight 1. Daarnaast biedt het goede mogelijkheden om te communiceren met verschillende databronnen. Zo ondersteunt het o.a. het gebruik van web services en rss-feeds. Het feit dat controls belangrijk zijn voor de adoptie van Silverlight bij ontwikkelaars is ook bij Microsoft geland. Mede om die reden zal Microsoft tijdens de PDC (eind oktober) een nieuw team aankondigen – of op het moment dat u dit leest, aangekondigd hebben - dat zich enkel bezig gaat houden met het ontwikkelen van controls voor Silverlight 2 en WPF. De verwachting is dat uiteindelijk 100 controls beschikbaar komen voor Silverlight 2. Eenvoudige presentatie van content De Silverlight 2-plugin is onderdeel van de browser waardoor de applicaties ook in de browser 'leven'. Voor het presenteren van data afkomstig van SharePoint zal het moeten communiceren met een service. SharePoint beschikt over een aantal services waarmee de content van het systeem opgehaald en bewerkt kan worden. Dat nog niet alles koek en ei is, blijkt uit de SharePoint-webservices die een datatype retourneren dat de Silverlight 2-runtime niet kent. Dit betekent dat de ontwikkelaar grofweg twee keuzes heeft: of hij ontwikkelt een eigen service-implementatie of hij ontleedt het XML-resultaat van de Sharepoint-webservice. Zodra de data ontvangen is, kan deze
gepresenteerd worden aan de gebruiker. Hiervoor kan dus gebruik gemaakt worden van de controls die Silverlight 2 biedt. Juist het gebruik van de controls geven Microsoft en Adobe met hun plugins een voorsprong omdat Google op basis van Javascript en HTML werkt en hierdoor dit moeilijk kan invullen. Word previewer Voor documenten die opgeslagen zijn in SharePoint op basis van het nieuwe Office OpenXML-formaat (OOXML, tevens het Office 2007-formaat) biedt Silverlight 2 nieuwe mogelijkheden. Zoals de naam van de technologie doet vermoeden is OOXML volledig gebaseerd op XML. Hierdoor is het goed mogelijk een service te ontwikkelen die b.v. behalve de titel en de auteur van het document ook de eerste drie pagina's van het betreffende document ophaalt. De Silverlight-applicatie kan vervolgens de metadata van het document tonen, met daarbij een preview van de eerste paar bladzijden van het document. Het bedrijf Intergen uit Nieuw Zeeland lanceerde tijdens het MIX 2008-event in Las Vegas zelfs een volledige OOXML-reader voor Microsoft Worddocumenten. De oplossing is gebouwd in Silverlight 2 en geeft gebruikers de mogelijkheid om documenten te lezen vanuit de browser, zonder dat hiervoor Microsoft Word is vereist. RSS feed De makkelijkste manier om SharePoint content te tonen in een Silverlight-applicatie is door gebruik te maken van een RSS-feed. Op basis van de _layouts/listfeed.aspx pagina en de list guid als querystring parameter kan voor elke lijst de RSS-feed worden aangroepen en de data worden opgehaald. De Silverlight-applicatie kan de listfeed aanroepen door gebruik te maken van het HttpWebRequest-object dat een url verwacht, in dit geval de url van de SharePoint RSS-feed. Doordat communicatie in Silverlight altijd asynchroon plaatsvindt, moeten we een callback functie meegeven die wordt uitgevoerd zodra er antwoord is. De SyndicationFeed kan op basis van een XmlReader het resultaat van de RSS-feed inlezen.
magazine voor software development 47
INFORMATION
WORKER UX public void GetRSSFeed() { // Roep de RSS feed aan string rssUrl = "http://localhost/News/_layouts/" + "listfeed.aspx?List= %7B965400DD%2D34DF%2D49EA%2DA929%2D28FA4740AB66%7D"; HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create( new Uri(_RSSUrl)); request.BeginGetResponse(new AsyncCallback( RSSFeedResponseCallback), request); } public void RSSFeedResponseCallback( IAsyncResult asyncCallback) { HttpWebRequest request = (HttpWebRequest)asyncCallback.AsyncState; HttpWebResponse response = (HttpWebResponse)request.EndGetResponse( asyncCallback); XmlReader reader = XmlReader.Create(response.GetResponseStream()); SyndicationFeed feed = SyndicationFeed.Load(reader); Dispatcher.BeginInvoke(delegate { // Zet de text property van het textblock (xaml) MyTextBlock.Text = feed.Items.ToList()[0].Title.Text; myStoryboard.Begin(); } ); }
Hoge kwaliteit streaming video De veelal ongestructureerde verzameling gegevens die we opslaan in SharePoint kan divers zijn. Een nieuwe trend is om naast teksten, documenten en foto's ook video- en audiobestanden vast te leggen. Video- en audiobestanden kunnen, afhankelijk van de kwaliteit en lengte, qua omvang oplopen tot enkele honderden Mb’s. Dit maakt deze bestanden minder geschikt voor het opslaan in SharePoint. Daarnaast biedt het beperkte mogelijkheden om deze video- of audiobestanden op basis van streaming aan te bieden. Met behulp van Silverlight kan hoge kwaliteit video en audio online getoond worden. Hiervoor wordt de video of audio op basis van Silverlight streaming aangeboden. Voor het streamen van video kan er gebruik gemaakt worden van een aparte streaming server of van de gratis dienst Microsft Silverlight Streaming Service. De dienst biedt net als YouTube de mogelijkheid om je videobestand te uploaden naar de streaming server. Het plaatsen van het videobestand op een streaming server maakt de video nog niet bekend in SharePoint. Om dit te bereiken zal de metadata, zoals het onderwerp, titel, auteur en overige gegevens, over de video opgenomen moeten worden in SharePoint. Op basis van deze metadata kunnen specifieke kenmerken van een ecm-systeem, zoals zoeken, rechtenbeheer en workflow, worden gebruikt. Het opensource project “Podcasting Kit for SharePoint” (PKS) maakt gebruik van deze concepten, en biedt een oplossing voor hoge kwaliteit video toepassingen in SharePoint. De oplossing bestaat uit een sitedefinitie met daarin een aantal document libraries voor het opslaan van de metadata van de video’s. Voor het uploaden van de video’s maakt het gebruik van een Silverlight control.
Listing 1: RSS-reader in Silverlight De volledige source code is beschikbaar op de website http://www.sdn.nl. Complex ecm-systeem: de gebruiker merkt niets SharePoint is veelzijdig, maar daarbij ook complex. Voor gebruikers die er dagelijks mee werken zal dit geen probleem zijn. Zij zullen zich de eigenschappen en manier van werken eigen maken. Voor medewerkers die incidenteel gebruik maken van het systeem kan dit wel een drempel vormen. Een manier om de complexiteit van delen van het systeem weg te nemen is door hier een eigen user interface voor te maken. Deze schermt de complexiteit van het ecm-systeem af. Met behulp van Silverlight is het vrij eenvoudig functionaliteit als drag and drop, fotobewerking en het uploaden van meerdere documenten te realiseren. Het voordeel hiervan is dat de gebruiker geen kennis hoeft te hebben van de complexiteit die SharePoint of het user interface met zich meebrengt en dat er hele specifieke toepassingen gemaakt kunnen worden voor bepaalde doelgroepen.
Het voordeel is dat er hele specifieke toepassingen gemaakt kunnen worden voor bepaalde doelgroepen Mobiele gebruikers kunnen een dergelijke doelgroep vormen. SharePoint biedt op dit moment beperkte ondersteuning voor mobiele apparaten. De verwachting is dat vanaf 2009 ook Silverlight 2 voor Windows Mobile en enkele Nokia-telefoons beschikbaar komt. Hierdoor wordt het mogelijk om specifieke functionaliteit van ShareSOFTWARE DEVELOPMENT NETWORK Point te ontsluiten met een rijke gebruikersinterface voor de mobiele gebruikers.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
48 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Fig. 1: Silverlight upload-control van de PKS De control voorziet in de mogelijkheid om meerdere video’s parallel te uploaden en houdt daarbij de netwerkverbinding in de gaten. Na het uploaden wordt door een Windows service een preview gemaakt van de video en zorgt de service ervoor dat de geuploade video geschikt wordt gemaakt voor Silverlight streaming. Conclusie De mogelijkheden van Rich Internet Applications zijn talrijk en ze vormen de nieuwe tendens waarbij websites veranderen van platte HTML naar rijke user interfaces. Met de komst van Silverlight lijkt Microsoft wel een antwoord te hebben gevonden op Adobe, dat al langere tijd aanwezig is op het gebied van interactieve content voor het web. Mijns inziens zal Google met de open source-technologie Ajax vooral in het visuele aspect moeten inboeten ten opzichte van de plugins die Adobe en Microsoft bieden •
Donald Hessing Donald Hessing is architect en technisch teamleider bij de unit Microsoft .NET System Development van VX Company IT Services BV. Hij heeft een brede interesse in techniek, en vervult o.a. de rol van lead architect binnen het Microsoft SharePoint team. Zijn focus ligt op het ontwikkelen van maatwerk oplossingen op basis van SharePoint en het Microsoft .Net Framework. Donald is de afgelopen jaren als technisch teamleider of architect verantwoordelijk geweest voor implementaties bij onder andere Interpolis, CZ en BCC. Naast zijn dagelijkse werkzaamheden geeft Donald regelmatig presentaties over praktische toepassingen van Microsoft technologie voor andere het Software Development Network en de Microsoft Developers Days.
Delphi
TIP:
Delphi, dbExpress en de SQL zandloper Als je dbExpress gebruikt als data-access laag, dan zal het wellicht opgevallen zijn dat er alleen nog maar "gewone" zandlopers zichtbaar worden (tijdens het open van conneties of het uitvoeren van queries) en niet de SQL zandlopers (die we vroeger bij de BDE ook zagen). Dit is nog steeds zo in Delphi 2009 en 2007 (en was ook al bij Delphi 7 het geval). De oorzaak zit hem in de definitie van de constante HourGlassCursor in de unit SqlExpr.pas, die daarin de waarde -11 (HourGlass) krijgt in plaats van de waarde -17 (SQLWait). Wie liever de SQL zandloper terug wil, kan het volgende doen: 1. Kopieer SqlExpr.pas van de source\database directory naar je eigen project directory (zodat de compiler deze kopie zal gebruiken en niet de originele versie) 2. Open de unit SqlExpr.pas 3. Zoek de regel met de volgende code: HourGlassCursor = -11; (dat is regel 66 voor Delphi 2009, regel 62 voor Delphi 2007) 4. Maak er het volgende van: HourGlassCursor = -17; // was: -11 5. Build je project opnieuw. Nu zul je weer de SQL zandloper zien bij het gebruik van dbExpress. Let op: mochten er later updates komen van Delphi die de oorspronkelijke SqlExpr.pas unit aanpassen, dan moet je bovenstaande stappen opnieuw uitvoeren (anders blijf je de SqlExpr.pas unit gebruiken zonder de eventuele nieuwe updates erin).
Advertentie Aladdin
CORE SYSTEMS
Michelle Cordes en Christiaan Heidema
The Best of Both Worlds:
Modernizing Core Sytems Core systems have been in place for the last 40 years. The last 10 years, however, the ICT environment has changed dramatically by developments such as Client Server, Internet and SOA. Central mainframe applications have been unlocked to client/server applications in the 90’s, via the internet around the millennium and now to SOA. Mainframe applications have been designed to fulfill one task only, while today’s ICT demands more flexibility. How do Core System engineers cope with this challenge? With the aid of tools and 4 decades of engineering craftsmanship developers can quickly identify the business rules embedded in core business processes, restructure code, remove dead code and create reusable components that can be enabled as services within an SOA. This combination goes by the name of Enterprise Modernization.
• Software delivery processes and infrastructure supporting asset discovery and reuse • Architectural approaches that leverage web technology enhancements • Organizational skills to rapidly develop cross-platform applications.
To modernize core systems, it is necessary to standardize and harmonize the software assets themselves as well as the software development process
Fig. 1: Overview Enterprise Modernization Enterprise Modernization: the process Enterprise Modernization involves leveraging existing systems, improving the skills of IT staff and modernizing the architecture and infrastructure. It might be clear that transforming these changes all at once will be a very risky and finally a costly operation. Therefore, it´s recommended to schedule the process of Enterprise Modernization in different steps. Determine the road to modernize Every company has to deal with particular business-requirements and is facing different staffing constraints. Furthermore, not all IT departments have the same budgetary limitations. Therefore, before starting the Enterprise Modernization process, it is necessary to determine the ultimate destination of the modernization process and clearly define SOFTWARE DEVELOPMENT NETWORK the steps to reach the point on the horizon. This IT Modernization roadmap should acknowledge a holistic view of:
FULL-COLOUR
50 cyaan 100 geel
Standardize and Harmonize To successful accomplish the modernization initiative, it is necessary to standardize and harmonize the software assets themselves as well as the software development process. Therefore, each modernization approach starts with assessing and reviewing existing systems to establish a common repository of the key application assets. Together with the knowledge received by these system reviews, this repository enables development teams to quickly discover and understand application relationships and dependencies, as well as assess the impact of proposed changes. The focus of development teams will be on a higher level, because their view is lifted up from one single application to more applications, including the underlying relationships. By using an integrated development environment for all platforms, development teams are able to break down the walls of their own development islands. All teams are working in according with the same standards and products and will anticipate and collaborate with other teams easily. Knowledge can also be shared across teams and will make the software development more flexible. Extending the integrated development environment with appropriate tools will lift up your software lifecycle-, change- and releasemanagement. Version control, deployment en defect tracking is done for all systems in the same way and affect the quality of your Software Lifecycle.
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
50 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Re-factor assets After the standardization and harmonization activities have been completed, you have established a solid foundation to proceed the modernization process. The next step of the modernization initiative is
CORE SYSTEMS to re-factor parts of the applications. During this step developers will identify the embedded business rules. When the business rules are determined, it is possible to restructure the assets and create reusable components. Dead code can be identified and removed. Reuse components The final step in the Enterprise Modernization approach is to invest in new opportunities. Because applications are transformed into more or less reusable components, extension of the functionality of applications will be much easier and cheaper than before. Consequently more budget is available for investing in new technologies and innovations like: • Composite application development (SOA) • Extend your applications by a web-based user-interface • Transform towards business language The approach of Enterprise Modernization ensures the opportunities to achieve this higher level of software development. Applications are more flexible, reusable and easier to maintain and therefore increase business responsiveness to market demands and business requirements. Enterprise Modernization: the software perspective Now let’s look at enterprise modernization from a software or application perspective. Core systems are facing enterprise modernization issues such as maintenance costs, using existing assets, architectural complexity, teams working in silos, cross-platform support and skills. The goal of Enterprise Modernization is to provide the capabilities that an organization requires to cost-effectively and incrementally evolve enterprise systems toward modern architectures and technologies. Tooling can help you to adapt business processes quickly and flexibly by reusing existing applications and data. Developers can unite and use their skills in disparate programming languages and work within a single, integrated development environment. This environment helps to form the foundation for your enterprise modernization initiatives.
In addition, asset modernization tools can help you: • Manage and govern the design, development and consumption of software assets and services; • Eliminate the need to research, catalog and assemble the information for each service request; • Identify assets that could be affected by proposed changes; • Reduce the cost of ongoing application maintenance; • Shorten the learning curve for new developers; • Improve the productivity of existing IT staff.
Asset modernization tools analyze the business software and identify components that can be reused Asset modernization tools can help analyze the business software and identify components that can be reused. Component identification within their applications provides an organization the ability to modernize incrementally to minimize risk and costs. According to Jeffery Poulin and Brent Carlson, the industry-average cost to develop new software is approximately €75 per line. “This means that every 1,000 lines of reused code yields a Development Cost Avoidance of €60,000!” This certainly makes the case for reusing existing code in any modernization effort.
“Every 1,000 lines of reused code yields a Development Cost Avoidance of €60,000!” Support the lifecycle of assets When you begin the process of creating reusable assets, it is also important to properly manage and govern your services. By establishing a comprehensive picture of your software assets, you can improve asset reuse. In turn this can help you to quickly deliver innovative IT solutions, control costs, reduce application backlogs and improve business flexibility and responsiveness. Asset management tooling can help improve productivity and software delivery through asset reuse. This enables you to create, modify, govern and locate any type of development asset, including SOA and systems development assets. Analyze existing enterprise applications Often, companies have hundreds of project teams simultaneously making changes to application code and data structures.
Fig. 2: Overview Asset Modernization Asset modernization: extending the value of existing enterprise assets Successful modernization initiatives require deep insight into targeted applications. Asset discovery and transformation tools can help your development teams generate detailed reports and graphics that enable rich understanding of existing applications. With this knowledge, developers can quickly identify the business rules embedded in core business processes, restructure code, remove dead code and create reusable components that can be enabled as services within an SOA. Fig. 3: Rational Asset Analyzer
magazine voor software development 51
CORE SYSTEMS An automated asset-analyzer application, such as IBM Rational Asset Analyzer, can help engineers to cut through the complexity of the applications and interdependencies across the core systems to increase productivity and improve product quality. Rational Asset Analyzer scans mainframe and distributed software assets, stores related application information in a repository, and makes the data available in graphical and textual format through a web browser. Transform existing software assets into reusable components A key part of Enterprise Modernization is to identify reusable components and business rules that can be extracted and converted into a web service. Identifying these rules can help accelerate strategic and tactical modernization initiatives by allowing development teams to quickly transform existing assets and discover reusable business logic for creating services.
A key part of Enterprise Modernization is to identify reusable components and business rules that can be extracted and converted into a web service Architecture modernization: driving innovation with technology advancements A modernization initiative must also address the complex dimensions of architecture. Fragmented business processes, workflows, data and tightly coupled application architectures reduce your flexibility and agility. To transform your core systems into flexible applications and services while avoiding costly and high-risk rip-and-replace approaches, you can work with what you already have. Architecture modernization can help to reduce time to market, improve business alignment for growth, cut costs and limit business risk. Design and construction tools are designed to: • Speed the efficiency of core system development, web development and integrated mixed-workload development; • Break skills silos by simplifying and accelerating cross-platform development; • Increase productivity and reduce training costs by extending host applications to modern user interfaces; • Accelerate the adoption of SOA by rendering existing IT assets as service components, which encourages reuse and efficiency; • Create enterprise data standards, verify compliance and generate compliant models. Easily extend your applications to the web with reduced cost and risk Making existing mainframe applications available through the web can help extend their value while increasing efficiency and promoting asset reuse. Web applications provide a standard and easy-to-use graphical user interface (GUI) for your core system. These applications include portlets, rich client applications and applications targeted for browsers on mobile devices. Skills modernization: using and modernizing existing and new skills Traditional IT professionals have decades of experience and domain knowledge. The question is: how do you use this experience to improve your current enterprise applications and take advantage of the new architectures and technologies that are available on these platforms? An example of a tool that supports high development productivity is the platform-neutral, business-oriented Enterprise Generation Language (EGL). Because it’s platform independent, EGL SOFTWARE DEVELOPMENT NETWORK enables developers to build cross-platform applications and automatically generate and deploy native Java and COBOL code that’s
optimized for the target platform. EGL hides the details of the target execution platform and associated middleware, enabling developers to focus on the business problem rather than on the underlying implementation technologies. Even developers with little or no experience with Java and web technologies can use EGL to create enterprise-class services and applications quickly and easily. EGL can help you: • Use new technologies and innovation without retraining the existing staff; • Assign new employees to any project, no matter what the target platform is; • Speed the efficiency of core system development, web development and integrated composite application development. Using EGL, a major advancement in business languages Because larger enterprises can have numerous development platforms and skill sets, a platform-neutral development approach can help eliminate skills silos and create a unified pool of business-oriented developers who can be freely shifted across projects according to business demands. EGL is a programming language specifically designed to help business-oriented developers leverage the benefits of mainframe and Java platforms without having to learn all the details. Enabling your developers to focus on the business problem, EGL features high-level specifications that let developers quickly write fully functional applications and services. IBM Rational Business Developer software enables developers to write business logic in EGL source code and then generate Java or COBOL code along with the run-time artifacts required to deploy the application to the desired execution platform. import com.acme.ordersys.api.*;
handler Client type JSFHandler{view=CustomerOrders.jsp} // Service references
orderSvsIOrderService{@bindService{“OrderService"}; utilsClientUtils{ @bindService};
// Fields bound to JSF components in the JSP
custid CustomerId{ inputRequired= yes, minInput=9 }; orders Order[];
// Function bound to some button on JSP Function getOrders() try
orders = orderSvs.getOrdersForCustomer(custid); …
onException( ex ServiceInvocationException) end
end
// Other event handlers
end
Listing 1: Example of EGL Client Code acme.ordersys.api.*;
Service OrderService implements IOrderService Function getOrdersForCustomers(
Custid CustomerID) returns(Order[]) orders Order[];
get orders using custid; return( orders );
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
52 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
…
End
end
Listing 2: Example of EGL Service Code
CORE SYSTEMS EGL is easy to learn, whether your background is procedural or object oriented programming. We encourage you to join the growing community of EGL users and discover how this revolutionary technology can help you deliver innovative business solutions in record time. Process and infrastructure modernization: improving team collaboration and responsiveness Organizations have traditionally managed mainframe development separately from other platform developments. However, this separation not only can hinder collaboration and productivity across the software life cycle, it can also lead to errors that result in application failure or downtime. Tools for process, quality, and change and release management help automate and enforce development processes and enhance collaboration and productivity across multiple operating platforms throughout the application life cycle. These tools help you: • Enforce software governance policies and procedures across functionally diverse and geographically distributed teams; • Ensure that business goals and requirements drive downstream design, development and testing; • Lower costs by eliminating duplicate tools and processes; • Realize improved end-to-end communication and traceability across the life cycle; • Verify software builds and document the exact software versions that are deployed; • Manage quality across the software-delivery life cycle; • Strategically integrate application security throughout the softwaredevelopment life cycle; • Validate the scalability and reliability of complex applications before deployment. Govern change and release processes Solutions for change and release management can help boost productivity, improve visibility into projects and processes, unite distributed teams, and provide audit trails and traceability across the software-development life cycle for fast delivery of high-quality software. Tools can unify workflows for software configuration management and software change management. They can also help manage the software-development life cycle by using built-in replication and synchronization capabilities to provide integrated version control, workflow management and defect tracking. Enterprise change management can help protect your software assets globally, and help ensure that changes are linked to approved requests and that those changes are driven by a valid business requirement. Development investment modernization: enabling business flexibility Modernizing how you invest your development expenses is the final key to enterprise modernization. Investment in modernization includes moving investments to key platforms, architectures, and applications that can return maximum ROI. Organizations that continue to rely on inefficient existing applications and non-relational databases are finding that their ongoing maintenance costs are skyrocketing. To avoid this scenario, you need to make the transition to open, modular and proven software-development platforms that span the entire softwaredelivery life cycle.
Make the transition to open, modular and proven software-development platforms that span the entire softwaredelivery life cycle Conclusion By modernizing core systems your applications will become more understandable, less complex and easy to extend. Business rules and complex interfaces between applications can be unlocked and transformed into solid and clear pieces of code. Spaghetti is transformed into smooth lasagna. •
Michelle Cordes Michelle Cordes has been working with IBM for more than 20 years. In addition to technical sales and marketing roles, she has helped customers in many industries to solve their business problems using IBM System z developer solutions. Michelle can be reached at [email protected].
Christiaan Heidema Christiaan Heidema is Mainframe Software Guru at Sogeti Nederland BV. During the last 12+ year Christiaan has worked in several mainframe environments as DBA, Engineer and Change- and Configuration-manager. He developed a passion for mainframe software solutions. His expertise encloses inter alia COBOL, DB2, IMS, CICS and JCL.
.Net
TIP:
CSLA .NET for Silverlight Business applicaties maken met Silverlight 2? Rocky Lhotka heeft nu ook een versie van zijn populaire CSLA.NET framework voor het gebruik met Silverlight 2. Zie http://www.lhotka.net/weblog/OverviewOfCSLANET36ForWindowsAndSilverlight.aspx voor meer details.
magazine voor software development 53
.NET C#
Sander van de Velde
Reflection als Inpakpapier Probleemstelling Tijdens een recentelijk pilot-project is een nieuwe serviceinterface ontworpen om informatie uit te wisselen tussen twee bedrijven. Een webservice buiten de firewall (in de demilitarized zone, DMZ) moest als cliënt een applicatieserver binnen de firewall aanroepen. De oplossing was in eerste instantie in Windows Communication Foundation (WCF) ontwikkeld. Hierin is een interfacedefinitie namelijk snel geschreven en dan moeten alleen nog de WCF attributen ServiceContract en OperationContract op de serviceinterface en de bijbehorende methodes geplaatst worden (kijk voor meer informatie over WCF op www.netfx3.com). Helaas bleek in een later stadium, dat op de webserver maximaal het .Net 2.0 framework beschikbaar was en dus moesten we voor de communicatie uitwijken naar .Net Remoting. Met .Net Remoting was eenzelfde oplossing met dezelfde methodes op een andere interfacedefinitie te bouwen. Maar één duidelijk verschil met WCF viel direct op: bij WCF maken de cliënt-proxy en de server van dezelfde service-interface gebruik, waardoor het in de WCF attributen nodig is om een referentie te leggen naar System.ServiceModel op de cliënt, de aanroepende partij. Nu lijkt dit op zich triviaal, deze assembly is namelijk gewoon geïnstalleerd met het .Net framework en dus altijd overal aanwezig. Maar opvallender is dat de gebruikte manier van communiceren zichtbaar wordt voor de aanroepende cliënt en dat het zelfs afdwingt, dat bepaalde gerefereerde assemblies geladen moeten worden. De WCF interfacedefinitie is dus een beetje opdringerig!
De WCF interfacedefinitie is dus een beetje opdringerig Dit zou opgelost kunnen worden door gebruik te maken van een factory design pattern voor de communicatie. Design patterns zijn standaard oplossingen voor standaard problemen (zoals hier de abstractie van de communicatie) en binnen Atos Origin proberen wij altijd eerst terug te grijpen SOFTWARE DEVELOPMENT NETWORK naar deze bewezen oplossingen. (Meer weten over design patterns? www.dofactory.com)
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
54 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Fig. 1: Class diagram van factory pattern: de twee interfacedefinities zijn helaas niet gelijk Bij een factory design pattern wordt een instantie van het interface opgevraagd om bepaalde werkzaamheden uit te voeren, zonder kennis te nemen van de gemaakte keuze. De beslissing welke klasse wordt geïnstantieerd zal door de factory gemaakt worden. De factory kan bijvoorbeeld afhankelijk van het aanwezige .Net platform de keuze tussen .Net Remoting of WCF maken (zie figuur 1). Maar omdat de twee genoemde technieken ieder een eigen interfacedefinitie vereisen, met of zonder WCF attributen, moet hier een tweede design pattern toegepast worden. Wrapper design pattern In eerste instantie lijkt het niet mogelijk om WCF zonder de benodigde WCF attributen te laten communiceren. De WCF Proxy moet de benodigde kennis over de interface bezitten. Daarom is onderzocht of het mogelijk is om de cliënt WCF-proxy wel te blijven aanroepen, maar deze proxy ‘in te pakken’ met een andere interface. De WCF-proxy aanroep moet tenslotte ergens uitgevoerd worden. Omdat hiermee alle communicatietechnieken met een uniforme, abstracte interface zijn aan te roepen, kan de factory de meest optimale techniek toepassen zonder dat de aanroepende cliënt zelf kennis over de communicatie krijgt. De oplossing voor dit probleem kan uitgewerkt worden in het wrapper design pattern (ook wel adapter genoemd). Dit wrapper design pattern wordt vaak toegepast, het is een fraaie manier om klassen samen te laten werken, die anders niet goed op elkaar aansluiten. De nieuwe interface wordt letterlijk als inpakpapier rond de aan te roepen logica gelegd (zie figuur 2).
.NET C# Maar inpakken zoals in het voorbeeld wordt al snel monnikenwerk vanwege het uittypen, vooral als het interface tijdens de ontwikkeling aan veel wijzigingen onderhevig is. Uiteindelijk bleek het gebruik van reflection samen met dynamische code generatie uitkomst te bieden om deze wrappers runtime te genereren zonder steeds extra te moeten coderen.
Fig. 2: Class diagram van wrapper design pattern De wrapper ondersteunt geheel of gedeeltelijk de logica van de ingepakte instantie, maar laat zich aanroepen met zijn eigen interface. De IWrapperInterface heeft hierbij geen enkele relatie met IWrappedInterface. IWrapperInterface hoeft dus niet eens alle members van IWrappedInterface te ondersteunen (zie listing 1). Het is nu ook mogelijk om bepaalde complexiteit van het ingepakte object bij het aanroepen te vereenvoudigen. public class WrapperClass : IWrapperInterface {
private IWrappedInterface _WrappedClass; public IWrappedInterface WrappedClass {
get {
}
}
return _WrappedClass;
public WrapperClass(IWrappedInterface wrappedClass) {
}
_WrappedClass = wrappedClass;
public string MethodOne(string parameter) {
}
return _WrappedClass.MethodOne(parameter);
public int MethodTwo(int parameter) {
}
}
return _WrappedClass.MethodTwo(parameter);
Listing 1: Een wrapper-implementatie met statische code Bij het voorbeeld in listing 1 zijn de twee interfaces wel gelijkvormig en daardoor is de wrapper zo dun mogelijk te houden. Toch kan met een wrapper nog meer gedaan worden dan alleen maar het doorlussen van de methodes. Zo kan bijvoorbeeld aan iedere aanroep logging, timing of extreme foutafhandeling toegevoegd worden.
Het gebruik van reflection samen met dynamische code generatie kan uitkomst bieden
Reflection en MSIL generatie Met reflection wordt het mogelijk nieuwe types, compleet met methodes en logica, runtime in het geheugen te brengen als Microsofts Intermediate Language (MSIL). Dit is de ultieme just-in-time (JIT) codegeneratie, maar in de praktijk zie je het maar weinig toegepast worden. Enerzijds omdat de veilige wereld van de code-editor en de design-time compiler verlaten moet worden. Anderzijds is de leercurve hoog want het komt dicht bij het zelf op de stack zetten van attributen, het aanroepen van methodes om de stack uit te lezen en weer verder te vullen. (Kijk voor meer details op http://msdn2.micro soft.com/en-US/library/8ffc3x75(VS.80).aspx) Wie ervaring heeft met assembler, beleeft hier het feest der herkenning. Toch is er een fundamenteel verschil met assembler. Ten eerste is er tegenwoordig IntelliSense en dat scheelt heel wat zoekwerk waar voorheen vaak een teksteditor het enige gereedschap was. Maar belangrijker is, dat deze MSIL wel degelijk typesafe is en blijft! Het is praktisch niet mogelijk om geheugenfouten te veroorzaken. De MSIL generator zal invalide opdrachten bij het samenstellen van het type gewoon afkeuren. Het gebruik van reflection betekent wel, dat ingeleverd moet worden op performance. Runtime MSIL generatie is echt enkele factoren trager dan het gebruik maken van voorgecompileerde code. Toch kan de runtime gegenereerde code opgeslagen worden, zodat later de assembly hergebruikt kan worden. Deze code zal dan geen snelheidsverlies veroorzaken. Dit opslaan van de assembly zal verderop gedemonstreerd worden. In System.Runtime.Remoting.Proxies wordt overigens de abstracte base class RealProxy aangeboden en deze geeft de mogelijkheid om twee interfaces op elkaar te mappen. Alle logica rond het inpakken wordt in één Invoke methode runtime uitgevoerd. Hierdoor is het geen vereiste dat de twee interfaces gelijkvormig zijn en is dus een andere mogelijke oplossing van de in dit artikel uitgewerkte oplossing. Wel zal de RealProxy altijd via reflection de mapping moeten uitvoeren en dus relatief trager blijven. Runtime wrapper generatie Onze wrapper gaat dus runtime twee willekeurige maar ‘gelijkvormige’ interfaces op elkaar laten aansluiten. Hiervoor is een static helper class geschreven om het gewenste type te genereren (zie listing 2). Om tot een dergelijke dynamische wrapper te komen moeten vier stappen doorlopen worden, waarna het gewenste type klaar voor gebruik is. Dus voor iedere gewenste wrapper tussen twee interfaces moet deze helper apart aangeroepen worden. // Step 1: Generate the wrapper class type TypeBuilder typeBuilder =
GenerateWrapperType(typeOfWrapperInterface);
// Remember the getter and setter MethodBuilder methodBuilderGet;
MethodBuilder methodBuilderSet; // Step 2: Generate the wrapped object property GenerateWrappedObjectProperty(
typeBuilder, typeOfWrappedInterface,
out methodBuilderGet, out methodBuilderSet);
// Step3: Generate the constructor GenerateConstructor(
magazine voor software development 55
.NET C# typeBuilder, typeOfWrappedInterface, methodBuilderSet); // Step 4: Generate all methods GenerateWrappedMethodes(
typeBuilder, typeOfWrappedInterface, methodBuilderGet);
// Finally, build this wrapper type and return return typeBuilder.CreateType();
Listing 2: De wrapper helper class aanroep met de vier stappen De eerste stap is van administratieve aard. Types kunnen niet gegenereerd worden zonder er een assembly voor te definiëren. Ook moet een module verplicht aanwezig zijn; dat is niet gelijk aan een namespace maar geeft wel de mogelijkheid om types te groeperen. Pas daarna is het mogelijk om met de bouw van het eigenlijke type te starten. Geef hierbij aan de typebuilder door dat een class aangemaakt moet worden en geef ook de overerving op (zie listing 3). Hier zal een overerving van de class Object de IWrapperInterface definitie gaan implementeren. // Define an assembly and a module AssemblyBuilder assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName(
typeOfWrapperInterface.Name + "ClassDll"),
AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder =
assemblyBuilder.DefineDynamicModule(
"Module" + typeOfWrapperInterface.Name);
// Finally, define the type of the wrapper TypeBuilder typeBuilder =
moduleBuilder.DefineType(
typeOfWrapperInterface.Name + "Class", TypeAttributes.Class
| TypeAttributes.Public,
typeof(object),
new Type[] { typeOfWrapperInterface });
return typeBuilder;
Listing 3: Beginnen met het definiëren van het wrapper type De tweede stap bestaat uit het mogelijk maken van het intern onthouden van het object dat de IWrappedInterface implementeert (zie listing 4). // The private field
FieldBuilder fieldBuilder = typeBuilder.DefineField(
"_WrappedObject", typeOfWrappedInterface, FieldAttributes.Private);
// The public property
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
"WrappedObject", PropertyAttributes.HasDefault, typeOfWrappedInterface, null);
// Define the public "get" accessor.
| MethodAttributes.HideBySig,
typeOfWrappedInterface, Type.EmptyTypes); ILGenerator iLGeneratorGet =
methodBuilderGet.GetILGenerator();
// Put 'this' on the stack
iLGeneratorGet.Emit(OpCodes.Ldarg_0); // Load the field on the stack
iLGeneratorGet.Emit(OpCodes.Ldfld, fieldBuilder); // Ready and return
iLGeneratorGet.Emit(OpCodes.Ret); // Define the private "set" accessor. methodBuilderSet =
typeBuilder.DefineMethod("setWrappedObject", MethodAttributes.Private
| MethodAttributes.SpecialName | MethodAttributes.HideBySig,
null, new Type[] { typeOfWrappedInterface }); ILGenerator iLGeneratorSet =
methodBuilderSet.GetILGenerator();
// Put 'this' on the stack
iLGeneratorSet.Emit(OpCodes.Ldarg_0); // Put 'value' passed on the stack
iLGeneratorSet.Emit(OpCodes.Ldarg_1);
// Put the 'value' passed in the private field
iLGeneratorSet.Emit(OpCodes.Stfld, fieldBuilder); // Ready and return
iLGeneratorSet.Emit(OpCodes.Ret); // Now inform the property to use this setter // and getter methods
propertyBuilder.SetGetMethod(methodBuilderGet);
propertyBuilder.SetSetMethod(methodBuilderSet);
Listing 4: De private field en de read-only property Eerst wordt een private field aangemaakt. Vervolgens wordt de publieke property gedefinieerd. Daarna worden de publieke getter en de private setter van de property gedefinieerd. Deze twee gedragen zich namelijk als methodes. In het algemeen voeren methodes code uit, dus die code moet ook gedefinieerd worden. Dit gebeurt met een ILGenerator en opcodes. Opcodes zijn de MSIL instructies, waarmee bijvoorbeeld doorgegeven parameters op de stack worden geplaatst en methodes worden uitgevoerd, die dan uiteraard van de stack lezen en er wellicht ook weer op terugschrijven (zie http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_members.aspx voor details over de hier gebruikte opcodes). Zoals wellicht bekend heeft een setter van een property een nogal magische ‘value’. Hier zie je dan ook hoe bij de setter methode gewoon al een waarde op de stack beschikbaar blijkt te zijn: Ldarg_1. De setter is dus inderdaad gewoon een methode. De getter en de setter worden teruggegeven aan de helper class, omdat we deze methodes later nog nodig hebben. De derde stap is het aanmaken van de constructor. De property wordt namelijk afgeschermd voor overschrijven, omdat de wrapper toch als IWrapperInterface wordt aangeroepen en die interface definitie kent geen property van het type IWrappedInterface (zie listing 5).
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
methodBuilderGet =
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
typeBuilder.DefineMethod("getWrappedObject", MethodAttributes.Public SOFTWARE DEVELOPMENT NETWORK
| MethodAttributes.SpecialName SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
56 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
De constructor ontvangt hierbij het ingepakte object als parameter en schrijft die simpelweg weg in het private field. Dit kan omdat een MethodBuilder een overerving is van een MethodInfo.
.NET C# // Define the constructor parameters
// Load the field on the stack iLGenerator.EmitCall( OpCodes.Call, methodBuilderGet, null);
Type[] constructorParameters =
new Type[] { wrappedInterfaceType };
// Put every passed parameter on the stack for (int j = 0; j < pia.Length; j++) { iLGenerator.Emit(OpCodes.Ldarg, j + 1); }
// Define the constructor
ConstructorBuilder constructorBuilder = builder.DefineConstructor( MethodAttributes.Public
| MethodAttributes.RTSpecialName,
CallingConventions.Standard, constructorParameters);
ILGenerator iLGenerator =
constructorBuilder.GetILGenerator();
// Put 'this' on the stack
iLGenerator.Emit(OpCodes.Ldarg_0);
// Put the wrapped object passed on the stack iLGenerator.Emit(OpCodes.Ldarg_1);
// Call the setter of the property iLGenerator.EmitCall(
OpCodes.Call, methodBuilderSet, null);
// Ready and return
iLGenerator.Emit(OpCodes.Ret);
Listing 5: De constructor vult de property Als laatste stap moeten alle methodes van IWrappedInterface ondersteund gaan worden. Voor iedere te ondersteunen methode moeten alle door te geven parameters op de stack geplaatst gaan worden en daarna moet de methode van het ingepakte object aangeroepen worden om de logica uit te voeren (zie listing 6). Hiervoor maken we gebruik van de getter van de property. Een eventueel geretourneerde waarde uit de aanroep naar de methode van het ingepakte object wordt uiteindelijk gewoon weer terug op de stack gezet. MethodInfo[] methodInfosWrappedInterface = typeOfWrappedInterface.GetMethods(); foreach (MethodInfo methodInfoInterfaceType in methodInfosWrappedInterface) { //Put the parameter types in an array Type[] parameterTypes = new Type[ methodInfoInterfaceType.GetParameters().Length]; ParameterInfo[] pia = methodInfoInterfaceType.GetParameters(); for (int i = 0; i < pia.Length; i++) { parameterTypes[i] = pia[i].ParameterType; } // Define the wrapper method for the wrapped method MethodBuilder methodBuilder = typeBuilder.DefineMethod( methodInfoInterfaceType.Name, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot, methodInfoInterfaceType.ReturnType, parameterTypes); ILGenerator iLGenerator = methodBuilder.GetILGenerator(); // Put 'this' on the stack iLGenerator.Emit(OpCodes.Ldarg_0);
}
// Call the method of the wrapped object iLGenerator.Emit(OpCodes.Callvirt, methodInfoInterfaceType); // Ready and return iLGenerator.Emit(OpCodes.Ret);
Listing 6: Alle methodes overnemen en doorlussen De MSIL generatie is afgerond. Kijk nu nog eens naar listing 2. Helemaal onderaan wordt vanuit de typebuilder het type gecreëerd. Mocht er zich nu ergens een probleem voor doen in de typebuilder (bijvoorbeeld een methode zonder voldoende of verkeerde parameters op de stack), dan zal hier een exception optreden. Als hier niet wordt geklaagd, dan hebben we nu eindelijk het gewenste type van de wrapper class. Om dit gegenereerde type te kunnen gebruiken zal dus de wrapper class geïnstantieerd worden, waarbij het ingepakte object aan de constructor meegegeven wordt (zie listing 7). Het gegenereerde type wordt dus via een activator in een runtime object omgezet, welke de IWrappedInterface ondersteunt. // Make an instance of the wrapped class IWrappedInterface wrappedclass = new WrappedClass(); // Create the type of the wrapper Type wrapperType = WrapperHelper.CreateWrapperType( typeof(IWrapperInterface), typeof(IWrappedInterface)); // Make an instance of the type of the wrapper IWrapperInterface wrapper = (IWrapperInterface)Activator.CreateInstance( wrapperType, new object[] { wrappedclass }); // Call the wrapper instance string returnValue = wrapper. MethodOne("It is a wrap!"); Console.WriteLine(returnValue);
Listing 7: Wrapper creëren en aanroepen Zelfreflectie voor de wrapper Met het gebruik van de wrapper is aangetoond, dat de wrapper-helper werkelijk iedere willekeurige interfacedefinitie met een andere interface definitie kan inpakken. Het is mogelijk om te controleren wat nu runtime gegenereerd wordt door de gegeneerde assembly op te slaan. Verander hiervoor de code in listing 3. De aanroep van DefineDynamicAssembly moet AssemblyBuilderAccess.RunAndSave meekrijgen zodat de assembly op schijf vastgelegd kan worden en de aanroep DefineDynamicModule moet een bestandsnaam meekrijgen (“bestandsnaam.dll”). Tevens moet de assemblyBuilder als out parameter doorgegeven worden, want die wordt na het uiteindelijke samenstellen van het type (via Type Builder.CreateType) aangeroepen met het veelzeggende assemblyBuilder.Save(“bestandsnaam.dll”).
magazine voor software development 57
.NET C#
De runtime opgeslagen assembly is later gewoon te hergebruiken De runtime opgeslagen assembly is later gewoon te hergebruiken. Hierdoor is enigszins de grootste tekortkoming van het runtime genereren van code teniet gedaan, namelijk het relatief trage genereren. Open met bijvoorbeeld ILDasm de gegenereerde code en herken hier de code die zojuist met de ILGenerator samengesteld is (zie listing 8).
Links: • http://www.netfx3.com • http://www.dofactory.com • http://msdn2.microsoft.com/en-US/library/8ffc3x75(VS.80).aspx • http://msdn.microsoft.com/en-us/library/system.reflection.emit. opcodes_members.aspx • http://www.lutzroeder.com/ • http://research.microsoft.com/~jhalleux/ •
.method public newslot virtual instance {
string MethodOne(string A_1) cil managed
Sander van de Velde
// Code size
Sander van de Velde is als senior .Net ontwikkelaar werkzaam voor Atos Origin BAS Microsoft op de High Tech Campus te Eindhoven. Als na het programmeren nog tijd over blijft, gaat hij zich bezig houden met het coachen van zijn drie zoontjes, motorrijden of zeilen. Hij is te bereiken via sander.vandevelde@ atosorigin.com
.maxstack IL_0000:
IL_0001:
18 (0x12)
2
ldarg.0 call
instance class
[ReflectionDocumentClassLibrary]
ReflectionDocumentClassLibrary. IWrappedInterface
IWrapperInterfaceClass:: getWrappedObject()
IL_0006:
ldarg
IL_000b:
nop
IL_000a:
IL_000c:
A_1
nop
callvirt
instance string
[ReflectionDocumentClassLibrary]
ReflectionDocumentClassLibrary.
IWrappedInterface::MethodOne(string)
IL_0011:
}
ret
.Net
TIP:
ASP.NET en JQuery Listing 8: MethodOne gezien door de ogen van IlDasm Het is zeker de moeite waard om dezelfde assembly ook eens met Reflector van Lutz Roeder te openen (http://www.lutzroeder.com/) Met deze tool is het mogelijk om MSIL code naar andere .Net talen (C#, VB.Net, Chrome, Delphi, etc) te ‘reflecteren’. Via de plug-in techniek van .Net Reflector wordt deze lijst van talen regelmatig uitgebreid en zo is het inmiddels ook mogelijk om de MSIL code om te zetten naar broncode zoals in listing 2 t/m 6 (research.microsoft.com/~jhalleux/ ReflectionEmitLanguage). Deze wrapper-helper is getest met het doorgeven van verschillende types. Het heeft geen moeite met value types, reference types, generic types, nullable types, etc. Ook kan voldoende omgegaan worden met exceptions vanuit de ingepakte klasse. Het feit dat de twee interface-definities ook echt op elkaar moeten aansluiten, wordt nog niet afgedwongen door deze te vergelijken. Ook wordt niet op een Null referentie getest voor het ingepakte object. Dit voorbeeld kan naar eigen inzicht aangepast worden en het uitbreiden met bijvoorbeeld logging van de aanroep met alle parameters erbij spreekt dan ook tot de verbeelding. Conclusie Voor mij was het bouwen van een dynamische wrapper een aangename kennismaking met MSIL. Ik heb gemerkt dat de leercurve eerst redelijk steil is, maar als je eenmaal bezig bent, valt alles redelijk te begrijpen. Een goede kennis van het .Net framework is wel een vereiste. MSIL generatie is ontzettend krachtig. Het wordt mogelijk om zaken die normaal private gedefinieerd zijn uit te lezen en hierdoor zijn heel krachtige oplossingen te bouwen die de compiler normaal gesproken niet toelaat. Met de getoonde tools kan ook goed bekeken worden, hoe bepaalde code-constructies in MSIL gerepresenteerd SOFTWARE DEVELOPMENT NETWORK worden. Dit is handig, want goede documentatie is schaars. Veel succes!
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
58 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
JQuery is een kleine maar zeer populaire open source JavaScript library voor het gebuik in de browser. Microsoft staat niet helemaal bekend om zijn ondersteuning van open source maar in dit geval hebben ze besloten om JQuery mee te leveren in plaats zelf het wiel opnieuw uit te vinden. En ze leveren het niet alleen mee maar ze gaan het zelfs officieel ondersteunen. Zie http:// weblogs.asp.net/scottgu/archive/2008/09/28/jquery-andmicrosoft.aspx voor meer informatie.
Delphi
TIP:
Delphi 2009 onder Windows Vista Als je Delphi 2009 onder Windows Vista wilt installeren dan moet je de install.exe met de "Run as Administrator" optie draaien (anders kunnen niet de juiste directories en registry entries geschreven worden). Tevens komen de download files onder Windows Vista op de volgende plek: C:\Users\<username>\Documents\Rad_Studio_Downloads (een andere plek dus dan onder XP of 2003). Ook bij het starten van Delphi 2009 onder Windows Vista kun je het beste de "Run as Administrator" optie blijven gebruiken overigens.
Advertentie Sira Holding b.v.
.NET
Piet Amersfoort
Een Eerste Introductie in F# Microsoft heeft ervoor gekozen met .NET verschillende typen programmeertalen te ondersteunen. Met de komst van F# wordt naast object georiënteerde, imperatieve, script- en dynamische talen ook een functionele programmeertaal aangeboden. Aan de hand van een aantal voorbeelden wordt in dit artikel getoond hoe F# werkt en hoe de bijbehorende programmeeromgeving gebruikt kan worden. Is keus goed? In de Computable verscheen na DevDays 2008 een artikel met de kop “Microsoft-ontwikkelaars hebben te veel keus”.
Tot 29 augustus 2008 was F# te downloaden van de de Microsoftresearch website. Nu is de eerste CTP uit. Uit het blog van Somasegar (2 september 2008):
#Computable#: Microsoft-ontwikkelaars kunnen kiezen uit zoveel verschillende platformen en talen dat ontwikkelaars door de bomen het bos niet meer zien
# Somasegar: Last week, the F# team released the F# September 2008 CTP. This release marks an important step along the path we laid out in October to integrate the F# language into Visual Studio, and to continue innovating and evolving F#.
Daar kan de volgende stelling van Bernard Baruch tegenover worden gezet: #Bernard Baruch#: If all you have is a hammer, everything looks like a nail Voor veel Microsoft-ontwikkelaars was er de keuze tussen C# en VB. Maar deze twee talen verschillen weinig van elkaar, ze zijn in veel opzichten een eeneiige tweeling. Het zijn beide object georiënteerde, imperatieve talen. Zo is het is bijvoorbeeld niet lastig een programma te schrijven dat VB-code vertaalt in C#-code en omgekeerd. In dit artikel zal F# aan de Microsoft-ontwikkelaar met .NET-kennis (bijvoorbeeld C# of VB) worden voorgesteld. In Computable-termen is F# een nieuwe boom in het functionele programmeerbos. Het doel is de ontwikkelaar ertoe te bewegen om F# te downloaden en ermee te experimenteren. Dit artikel bevat geen theoretische verhandeling over functionele programmeertalen, maar aan de hand van enkele voorbeelden zal getoond worden hoe er met F# gewerkt kan worden. Er is niet gekozen voor de bekende ontwerpen uit .NET, zoals object georiënteerd programmeren, hetgeen geen enkel probleem is met F#, maar voor voorbeelden waaruit de unieke eigenschappen van F# en de F#-programmeeromgeving blijken. De ontwikkelaar kan dan zelf bepalen welke programmeertaal het juiste middel is om een doel te bereiken. Het probleem is niet de keuzemogelijkheden van de ontwikkelaar, zoals Computable beweert. De ontwikkelaar moet ondersteund worden bij het maken van zijn keuzes, dat is de uitdaging. Wanneer heeft de ontwikkelaar een hamer nodig en wanneer een zaag?
F# is te gebruiken met .NET van versie 2.0 tot en met 3.5 en MONO en wordt ondersteund door Visual Studio 2008. Nadat F# is geïnstalleerd, is het eerste dat opvalt dat F# op meer manieren gebruikt kan worden dan bijvoorbeeld VB of C#. Zo kunnen er scripts geschreven worden in F#. Listing 1 bevat een eenvoudig voorbeeld. #light
printfn "Hallo SDN"
System.Console.ReadLine()
Listing 1: FsharpHalloSDN.fsx Als deze code in een bestand met de naam FsharpHalloSDN.fsx wordt opgeslagen, dan kan dit script direct vanuit de Windows-verkenner worden opgestart.
Fig. 1: F# Interactive Figuur 1 laat zien hoe dit in de verkenner eruit ziet en figuur 2 toont het resultaat.
De F#-programmeeromgeving Somasegar (Senior Vice President Developer Division van Microsoft) heeft op 17 oktober 2007 aangegeven dat F# een volwaardige .NETtaal wordt:
FULL-COLOUR
50 cyaan 100 geel
Fig. 2: F# Interactive Hallo SDN
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
# Somasegar: We will be partnering with Don Syme and others in Microsoft Research to fully integrate the F# language into Visual SOFTWARE DEVELOPMENT NETWORK Studio and continue innovating and evolving F#. In my mind, F# is another first-class programming language on the CLR. SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
60 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Hiermee is het verplichte hello-world-programma geschreven. Een aantal zaken valt op: • De instructie #light. F# kent twee varianten. Een variant waarbij het
.NET
einde van een instructie expliciet wordt aangegeven (dat lijkt op VB) en een variant waarbij spaties (4 spaties) aangeven wat de structuur van een instructie is. De laatste variant heeft in de beschikbare literatuur en op het internet de voorkeur. Het vreemde is echter dat de keuze voor deze variant expliciet moet worden aangegeven met de instructie #light. Deze eis zal hoogstwaarschijnlijk in de toekomst verdwijnen. • printfn is een instructie uit de F#-library en stuurt een string naar de console. • Om te voorkomen dat na het opstarten van de console deze direct weer verdwijnt, is de voor .NET-programmeurs vertrouwde System.Console.ReadLine-instructie toegevoegd. Hiermee is tevens aangetoond dat het .NET-bouwwerk direct vanuit F# te benaderen is. Naast de scriptvariant is er ook een interactieve variant. In de F#-console kunnen direct instructies worden ingevoerd. In figuur 3 is een functie plusEen gedefinieerd die de waarde van een getal n met één verhoogt.
Fig. 3: F# Interactive Na ‘let’ volgt de naam van de functie (plusEen) en de naam van de parameter (n). plusEen werkt met integers, er gaat een integer in en er komt een integer uit (int -> int). F# leest geen gedachten maar leidt uit het plusteken en de waarde 1 af dat n ook een integer moet zijn en weet dat het resultaat ook een integer zal zijn. Als vervolgens de plusEen van 3 wordt berekend. dan is het resultaat 4. Enkele andere zaken die opvallen: • Het keyword let. Met let wordt aangegeven dat een waarde (val) wordt gedefinieerd. • Een val is niet hetzelfde als een variabele, uit bovenstaand voorbeeld blijkt dat een functie ook een val is. • De waarde van een val is vast. In C# en VB komt die overeen met const/readonly. De waarde van vier is 4 en blijft 4. • ;; wordt in de interactieve omgeving gebuikt om het einde van een instructie aan te geven. Van F# Interactive zijn er twee varianten; de console-variant en de Visual Studio-variant. Zoals de naam al doet vermoeden werkt de laatste intensief samen met Visual Studio (VS). Hierdoor is het mogelijk om stukken code vanuit VS direct uit te voeren. Gebruikers van SQL Management Studio zullen dit herkennen. In figuur 4 wordt een stukje VS-code geselecteerd en met Alt+ Enter wordt deze uitgevoerd in VS-console.
Fig. 4: F# Interactive in Visual Studio met Alt-Enter
Een voordeel van deze wijze van werken is dat op een eenvoudige wijze code uitgevoerd en getest kan worden. Als laatste zijn er de voor de .NET-ontwikkelaar bekende mogelijkheden om code om te zetten in .dll- en .exe-bestanden met behulp van console-instructies of Visual Studio. Deze bestanden zijn dan de gebruikelijke .NET-bestanden en kunnen op elke computer waarop .NET geïnstalleerd is, gebruikt worden. Tevens zijn deze bestanden te gebruiken als bibliotheek voor andere .NET-ontwikkelaars in andere .NET-talen. Een voorbeeld van functioneel programmeren: priemgetallen In het volgende voorbeeld zal getoond worden hoe in F# een probleem functioneel kan worden aangepakt. Hierbij worden de verschillen van aanpak met andere programmeertalen getoond. Als eerste volgt een VBA-voorbeeld (listing 2) van de functie IsPrime, deze functie kan in Excel gebruikt worden om te bepalen of een getal een priemgetal is. Public Function IsPrime(value As Integer) As Boolean Dim i As Integer i = 2 IsPrime = True Select Case value Case 1 IsPrime = False Case 2 IsPrime = True Case Else Do If value / i = Int(value / i) Then IsPrime = False End If i = i + 1 Loop While i < value And IsPrime = True End Select End Function
Listing 2: VBA-functie die aangeeft of een getal een priemgetal is Met behulp van Google is de functie IsPrime gevonden op het internet en doet onder normale omstandigheden zijn werk. Voor wie het vergeten was: een priemgetal is een positief geheel getal dat alleen deelbaar is 1 en zichzelf. Nu zal getoond worden hoe in F# een functie gebouwd kan worden die hetzelfde resultaat oplevert (listing 3). #light let isDeelbaarDoor n m = n % m = 0 let rec bevatFactor n van tot = if van >= tot then false else if isDeelbaarDoor n van then true else bevatFactor n (van + 1) tot let isPriem n = match n with |1 -> false |2 -> true //staat er voor de duidelijkheid |_ -> not (bevatFactor n 2 (n - 1))
Listing 3: F# versie van isPriem
magazine voor software development 61
.NET
In een functionele programmeertaal zijn de bouwstenen functies. In bovenstaand voorbeeld is de functie isPriem opgebouwd met behulp van twee hulpfuncties: • isDeelbaarDoor wordt gebruikt om te bepalen of een getal deelbaar is door een ander getal, isDeelbaarDoor 10 5 = true, isDeelbaarDoor 10 6 = false. • bevatFactor wordt gebruikt om de deelbaarheid van een getal n door een reeks getallen te bepalen. De reeks loopt van het eerste getal: van tot het laatste getal: tot. bevatFactor 10 2 9 = true, bevatFactor 11 2 10 = false. Elke functie uit listing 3 illustreert enkele interessante aspecten van F#. Wat er opvalt bij de functie isDeelbaarDoor: • % is het modulo teken in F# (hiermee wordt de rest van een deling berekend). Een getal is deelbaar door een ander getal als de rest van de deling nul is. • In F# wordt niet de het dubbele isgelijkteken (==) gebruikt zoals in C#. • Na het eerste gelijkteken volgen op de volgende regel vier spaties. Omdat in de #light-modus gewerkt wordt, wordt hiermee aangegeven dat deze regel onderdeel van de definitie van isDeelbaarDoor is. • Weer heeft F# geen type-informatie nodig om te bepalen dat n en m integers zijn en dat het resultaat een boolean is (val isDeelbaarDoor : int -> int -> bool). • Er staan geen haakjes om de n en m. Het is mogelijk dit wel te doen (zie listing 4), maar daarmee verandert de functie van een functie met twee variabelen in een functie van één variabele (val isDeelbaarDoorAlt : int * int -> bool). isDeelbaarDoorAlt heeft als input een tuple van twee ints (int * int). Een tuple is een manier in F# een complex type te definiëren. In dit geval gaat de mogelijkheid verloren om met behulp van een functie van twee variabelen een nieuwe functie te definiëren met één variabele zoals in het voorbeeld uit listing 5. Op deze wijze een nieuwe functie definiëren wordt currying genoemd. Dit verklaart ook de schrijfwijze int -> int -> bool, als er een int wordt ingevuld dan ontstaat er een functie van int naar bool. #light
let isDeelbaarDoorAlt (n,m) = n % m = 0
Listing 4: alternatief voor isDeelbaarDoor #light
let isDeelbaarDoorTien n = isDeelbaarDoor n 10
• Met // kan er commentaar aan een regel worden toegevoegd. Blokken commentaar worden tussen ronde haken en astriksen geplaatst. Voorbeeld: (* commentaar *). In listing 6 is de functie bevatFactor uitgeschreven met behulp van pattern matching. #light let rec bevatFactorAlt n van tot = match n, van, tot with |_ when van >= tot -> false |_ when isDeelbaarDoor n van -> true -> |_ bevatFactorAlt n (van + 1) tot;;
Listing 6: Pattern matching versie van bevatFactor Nu de F#-versie van de isPriem-functie is geanalyseerd, kan deze worden vergeleken met de VBA-versie. Als eerste valt op dat de F#-versie veel dichter bij de definitie van priemgetallen staat. Natuurlijk is het mogelijk de VBA-versie te herschrijven in deelfuncties en recursie te gebruiken (huiswerk), maar dit is niet de natuurlijke manier van programmeren in VBA. Ten tweede worden er geen hulpvariabelen gebruikt (de variabele i in de VBA-versie). Een reden dat deze variabelen niet worden gebruikt is dat waarden in F# standaard niet kunnen veranderen. Zoals eerder is aangegeven zijn waarden per definitie vaste waarden. Als er een variabele moet worden aangemaakt dan moet dit expliciet worden aangegeven met het keyword mutable. In listing 7 is als eerste de waarde j gedefinieerd. F# leest de volgende regel als een logische expressie en deze levert natuurlijk false op. #light //waarde van j is en blijft twaalf let j = 12 j = 13 //dit levert false op (* in dit geval kan de waarde van i worden gewijzigd *) let mutable i = 5;; printfn "de waarde van i is nu: %d" i;; //de waarde van i is nu: 5 i <- 7;; printfn "de waarde van i is nu: %d" i;; //de waarde van i is nu: 7
Listing 5: voorbeeld van currying
Listing 7: Waarden zijn niet te veranderen, mutable variabelen wel
Wat opvalt bij de functie bevatFactor: • Na let volgt het keyword rec, dit is de afkorting van recursief. Een functie is recursief als deze zichzelf aanroept. • Eén van de eisen aan een recursieve functie is dat er stopcriterium is. In dit geval is een criterium dat de reeks geen getallen bevat (van = tot) of het tot-getal kleiner is dan het van-getal. Als er een getal gevonden wordt dat een factor van n is, is een ander stopcriterium.
De waarde van i kan wel gewijzigd worden; hiervoor wordt het <- symbool gebruikt. Het gebruik van mutable variabelen wordt over het algemeen afgeraden binnen functionele programmeertalen. In listing 7 wordt weer de functie printfn gebruikt. Met %d kan worden aangegeven dat op deze plek een cijfer zal worden afgedrukt, de waarde van i. Als laatste opmerking: in de F#-versie van de isPriem-functie wordt geen gebruik gemaakt van een loop-instructie. F# kent dit type instructies wel, maar ook deze dient men spaarzaam te gebruiken binnen het functioneel programmeren. Recursie heeft de voorkeur.
Wat opvalt bij de functie isPriem: • Naast de if then else constructie kent F# de mogelijkheid om patronen te matchen (pattern matching). In deze functie is een eenvoudig voorbeeld gebruikt. Pattern matching is te vergelijken met switch/select case in C#/VB maar biedt veel meer mogelijkheden. • Van het patroon dat als eerste aan de criteria voldoet wordt de actie (deSOFTWARE code DEVELOPMENT na de pijlNETWORK ->) uitgevoerd. Bij pattern matching is de underscore (_) de default, deze staat daarom onderaan in de lijst. Het resultaat in het geval n = 1 is daarom false.
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
62 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Voor het vervolg is er een functie nodig die het nde priemgetal kan bereken. Listing 8 bevat de code voor deze functie. Deze code bevat geen geheimen meer. volgendePriem is een hulpfunctie en berekent het eerst volgende priemgetal na een getal n (bijvoorbeeld volgendePriem 11 = 13). NdePriem berekent met behulp van patten matching en recursie het nde priemgetal.
.NET #light let rec volgendePriem n = if isPriem (n + 1) then n + 1 else volgendePriem (n + 1) let rec NdePriem n = match n with | 1 -> 2 |_ -> volgendePriem (NdePriem (n - 1))
Listing 8: Berekening van het Nde priemgetal Lijsten met priemgetallen Er zijn vele aspecten van F# die nog beschreven kunnen worden, bijvoorbeeld types in F#, samenwerking met C#, VB en COM, lexers en yacc, etc. Er is gekozen voor lijsten. Hiermee kan namelijk ook een ander aantrekkelijk aspect van functionele programmeertalen worden behandeld, laziness. #light let lijst1 = [1..35] let lijst2 = List.map (fun n -> NdePriem n) lijst1 let lijst3 = [for n in 1..35 -> NdePriem n]
Listing 9: Lijsten Een lijst (List) is in F# eenvoudig te definiëren. In listing 9 wordt als eerste een lijst gedefinieerd die alle getallen van 1 tot en met 35 bevat. Er kan een tweede lijst worden gedefinieerd door List.map los te laten op een functie die een integer in een integer omzet en een lijst. De functie die gebruikt wordt, is de functie die het nde priemgetal berekent (NdePriem) en de lijst zijn de getallen van 1 tot en met 35 (lijst1). Het resultaat is dus een lijst met de eerste 35 priemgetallen. Hetzelfde resultaat kan op de 3e manier bereikt worden; lijst3 is identiek aan lijst2. Het is dus mogelijk om in F# op heel eenvoudige wijze complexe lijsten te maken.
F# kent daarom laziness: waarden worden pas berekend als deze werkelijk nodig zijn
In het voorbeeld in listing 10 wordt een lijst priemgetallen met alle priemgetallen gedefinieerd. De belangrijkste punten zijn: • LazyList.unfold wordt gebruikt om de lijst te vullen. LizyList was onderdeel van F# en is met het uitbrengen van de eerste CTP verplaatst naar het PowerPack. • Met #r kunnen referenties gemaakt worden naar .dll-en waaraan standaard niet gerefereerd wordt (pas het versienummer aan aan de versie van F# die geïnstalleerd is). • De startwaarde is het eerste priemgetal 2. Met behulp van de functie volgendePriem wordt het volgende priemgetal berekend. • Seq.take is een functie die van een LazyList een deellijst maakt van het type LazyList, in dit geval de eerste 35 priemgetallen. • Seq.to_list is een functie die van het resultaat van Seq.take een lijst maakt. • Waarden worden pas berekend als deze nodig zijn. • De waarden worden bewaard. Als een waarde nogmaals gevraagd wordt, dan zal de waarde niet worden herberekend, maar direct beschikbaar zijn. Dit kan worden aangetoond door aan de functie volgendePriem een regel toe te voegen, zie listing 11. #light let rec volgendePriemAlt n = if isPriem (n + 1) then
printfn "de waarde is %d" (n + 1) //extra regel (neveneffecten)
n + 1
else
volgendePriemAlt (n + 1)
let priemgetallenAlt = LazyList.unfold (fun n 2
->
Some(n, volgendePriemAlt n))
let lijst5 = Seq.to_list (Seq.take 5 priemgetallenAlt) lijst5
let lijst6 = Seq.to_list (Seq.take 6 priemgetallenAlt) lijst6
Listing 11: Een aangepaste versie van de functie volgendePriem Het resultaat is zichtbaar in figuur 5. Als de waarden van lijst7 worden bepaald, dan wordt alleen het extra priemgetal berekend.
Een nadeel van deze manier van werken is dat op voorhand bekend moet zijn hoe groot de lijst gemaakt moet worden. Natuurlijk kan het risico op problemen worden verlaagd door een grote reeks te kiezen, maar dan kost het berekenen van deze getallen veel meer tijd en resources en er is een grote kans dat de informatie niet nodig is. F# kent daarom laziness (luiheid). Dit betekent dat waarden pas berekend zullen worden als deze werkelijk nodig zijn. #light #r @"C:\Program Files\FSharp-1.9.6.2\bin\ FSharp.PowerPack.dll" let priemgetallen = LazyList.unfold (fun n -> Some(n, volgendePriem n)) 2
let lijst4 = Seq.to_list (Seq.take 35 priemgetallen)
Listing 10: Laziness
Fig. 5: Lazyness in actie Het afdrukken van de waarde van n wordt in functionele programmeertalen een neveneffect (side effect) genoemd. Het wordt afgeraden gebruik te maken van neveneffecten. Als deze echter noodzakelijk zijn, dan is het devies deze te scheiden van de overige code. Door neveneffecten kan de toestand (state) aangepast worden. Als functies geen neveneffecten bevatten, dan is de uitkomst van deze functies totaal deterministisch. De uitkomst van de functie is onafhankelijk van de plaats en de tijd waarop de waarde wordt berekend.
magazine voor software development 63
.NET
Er is nog één probleem met de lijst met priemgetallen. Ook al is deze lijst oneindig groot, het is een lijst van integers. Door Euclides is al bewezen dat er oneindig veel priemgetallen zijn en dit heeft tot gevolg dat priemgetallen oneindig groot worden en dat kunnen integers niet. Ook voor dit probleem heeft F# een oplossing. F# kent het type bigint. Het prettige van het rekenen met bigint is dat de waarde theoretisch onbegrensd is en in praktijk bepaald wordt door de fysieke eigenschappen van de computer. Om aan te geven dat een getal van het type bigint is wordt aan een cijfer een extra I toegevoegd (bijvoorbeeld 6I). #light let isDeelbaarDoorBigint n m = n % m = 0I
Conclusie Dit was een eerste introductie in F# met als doel dat de lezer F# heeft gedownload of zal downloaden. Vele aspecten van F# zijn niet behandeld en er is geen theoretische verhandeling over functionele programmeertalen gegeven. Er zijn zelfs geen criteria gegeven wanneer F# een betere keus is dan C# of VB. Natuurlijk is er een aantal van de omstandigheden duidelijk geworden wanneer F# voordelen kan hebben. F# kan beter omgaan met grote getallen en oneindige verzamelingen, maar deze aspecten van F# zouden overgenomen kunnen worden in C# of VB. Wat echter uiteindelijk niet overgenomen kan worden, is de denk- en werkwijze achter een functionele programmeertaal. Dat de mogelijkheid er nu is deze werkwijze te gebruiken als dat nodig is, is goed. Keuze is goed. •
let rec bevatFactorBigint n van tot = if van >= tot then false else if isDeelbaarDoorBigint n van then true else bevatFactorBigint n (van + 1I) tot
Piet Amersfoort Piet Amersfoort is zelfstandig consultant op het gebied van bedrijfsautomatisering. Hij is op zoek naar problemen die meer zijn dan een uitdaging. Microsoft-technologie is één van zijn specialisme. Sinds 2007 werkt hij met F#.
let isPriemBigint n = match n with |1I -> false |2I -> true //staat er voor duidelijkheid |_ -> not (bevatFactorBigint n 2I (n - 1I)) let rec volgendePriemBigint n = if isPriemBigint (n + 1I) then n + 1I else volgendePriemBigint (n + 1I) let priemgetallenBigint = LazyList.unfold (fun n -> Some(n, volgendePriemBigint n)) 2I //test let priem = isPriemBigint 1923495699378933013323334356767I let lijst7 = Seq.to_list (Seq.take 6 priemgetallenBigint)
.Net
TIP:
.NET Framework Client Profile De volledige versie van het .NET framework is een flinke download. Voor veel applicaties is het hele framework echter niet nodig. Microsoft zag dit ook in en heeft nu een installatie beschikbaar die een subset, de .NET Framework Client Profile, installeert in plaats van de volledige versie. Later upgraden naar het volledige framework blijft uiteraard mogelijk. Zie http://windowsclient.net/wpf/wpf35/wpf-35sp1-client-profile.aspx voor meer informatie.
Listing 12: Een lijst met priemgetallen van het type bigint In listing 12 staan de aangepaste functies. Weer is F# in staat te bepalen wat de juiste types zijn. Het resultaat in figuur 6 toont dit aan. Alle functies werken nu met bigints. Tevens wordt van een groot getal bepaald of het een priemgetal is en de eerste zes priemgetallen worden berekend.
.Net
TIP:
ClickOnce and Firefox Een van de nuttige nieuwtjes in het .NET 3.5 service pack 1 is de Microsoft .NET Framework Assistant 1.0 die het mogelijk maakt om Click Once applicaties vanuit FireFox te starten. Zie http://www.hanselman.com/blog/FirefoxClickOnceXBAPsAndNET35SP1.aspx voor meer informatie. Fig. 6: Alle functies zijn omgezet in bigint-functies Er zijn nog diversie optimalisaties mogelijk, maar dat zijn de huiswerkopdrachten voor de lezer. Zo is het bijvoorbeeld mogelijk bevatFactor te herdefiniëren met als variabelen een bigint en een lijst. Vervolgens is hetSOFTWARE mogelijk de waarden in deze lijst drastisch te beperken. Zo is het DEVELOPMENT NETWORK bijvoorbeeld voldoende dat de getallen in de lijst priemgetallen zijn. Dit klinkt als recursie…
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
64 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
.NET ASP
Michiel van Otegem
ASP.NET onder de Motorkap:
Controls Maken Ik heb een haat/liefde verhouding met ASP.NET custom controls. Aan de ene kant vind ik de elegantie van custom controls geweldig, maar aan de andere kant zit ik altijd weer te stoeien met de juiste oplossing voor ViewState, event- en postback-handling en de rendering van HTML. Zo ook weer met een recente control die ik wilde bouwen, een control die ik de AddRemoveListBox noem. Het idee achter de AddRemoveListBox is simpel. Voor ontwikkelaars die de control gebruiken moet deze control zich gedragen als een ListBox waarbij meerdere items tegelijk geselecteerd kunnen worden. Een voorbeeld van de gebruikersinterface zie je in figuur 1. worden. Aan m’n tweede eis was nu echter nog niet voldaan. Aangezien de ListControl al alle items in de ViewState opslaat, is het overbodig dat de twee ListBox-controls voor de geselecteerde- en beschikbare items dat ook doen. Dit is simpel op te lossen door EnableViewState = false in te stellen op deze controls, maar dan werkt de PostBack afhandeling (en dus de hele control) niet meer. Een poging om dit te doen met de IPostBackDataHandler-interface mislukte. Deze interface gebruik je normaal gezien in een custom control om ervoor te zorgen dat data uit een PostBack afgehandeld wordt, maar door alles in child controls te regelen wordt de LoadPostData-methode op deze interface niet aangeroepen… wat nu?
Fig. 1: De AddRemoveListBox Een vergelijkbare control is wel beschikbaar van 3rd party vendors, maar met een groot ontwikkelteam gaat dat aardig in de papieren lopen. Ook gratis varianten die ik had bekeken deden net niet wat ik wilde. Perfectionistisch en eigenwijs als ik ben dacht ik zelf wel “eventjes” een variant te maken die wel aan mijn eisen voldeed. De belangrijkste eisen: 1) redelijk eenvoudig van opzet; 2) minimaal gebruik van ViewState. Het resultaat voldoet weliswaar aan m’n eisen en is goedkoper (voor ons team althans) dan kopen, maar toch kostte het meer moeite en zit er eigenlijk een lelijke hack in. Om de control eenvoudig te houden ben ik uitgegaan van System.Web.UI.WebControls.ListControl. Dit is een abstracte class die alles al in zich heeft voor ListBox-achtige functionaliteit, zoals een collectie van ListItem objecten en ondersteuning voor DataBinding. In feite hoef je alleen de UI te verzorgen. Om de code simpel te houden gebruik ik child controls, zodat de HTML rendering beperkt blijft tot het groeperen van de controls. De opmaak van child controls kun je wijzigbaar maken door de ControlStyle-eigenschap van de controls toegankelijk te maken voor de buitenwereld, zoals in listing 1. public Style AddButtonStyle {
}
get { return AddButton.ControlStyle; }
Listing 1: De stijl van een child control publiek maken Met twee Label-controls voor de kopjes, twee ListBox-controls voor de selectie en twee Button-controls voor de acties was ik klaar en had ik vrij snel een aardig werkende control die goed opgemaakt kon
IPostBackDataHandler moet het afhandelen van Postback data elegant maken, doordat je een collectie met data krijgt die alleen van toepassing is op de control zelf. Als deze niet werkt, moet je echter anders te werk gaan. Dan is het handig dat ASP.NET gelaagd is opgebouwd is, waardoor je terug kunt grijpen op het “good old” Request-object, die al in “Classic” ASP een belangrijke rol vervulde. Met behulp van de UniqueID van de child control waarvoor je de teruggestuurde data wilt opvragen, haal je de waarde(s) uit het Request-object. Omdat er sprake is van multi-select, kan een ListBox-control meerdere waardes retourneren. In dat geval retourneert Request.Form[myControl.UniqueID] een komma-gescheiden string, hetgeen niet handig is als een waarde al een komma bevat. Minder bekend is dat je ook Request.Form.GetValues(myControl.UniqueID) kunt gebruiken om een array met strings op te vragen, zoals in listing 2 gedaan wordt. private void AddItems_Click(object sender, EventArgs e){ String[] itemsToAdd =
Page.Requres.Form.GetValues(SelectList.UniqueID);
if(itemsToAdd != null && itemsToAdd.Length > 0) { foreach (String item in itemsToAdd) { } }
}
Items.FindByValue(item).Selected = true;
OnSelectedIndexChanged(new EventArgs());
Listing 2: Met het Request-object geselecteerd items uitvragen Listing 2 is een event handler voor de knop waarmee items toegevoegd kunnen worden aan de selectie. De waardes die geselecteerd zijn in de lijst met te selecteren items worden uitgelezen en aan de hand daarvan wordt het overeenkomstige item in de Items-collectie van de ListControl geselecteerd. De AddRemoveListBox-control kun je downloaden van mijn blog http://michiel.vanotegem.nl •
magazine voor software development 65
Advertentie Infodis
Advertentie iAnywhere Solutions/ a Sybase Company
ARCHITECTURE
Steef-Jan Wiggers
BizTalk Server:
Architectuur Sinds de eerste lancering wint BizTalk Server aan populariteit en zijn we inmiddels met BizTalk Server 2006 R2 beland bij de vijfde versie van het product. De huidige versie R2 is een uitbreiding op de vorige waarin een aantal functionaliteiten is toegevoegd zoals de ondersteuning voor native EDI en RFID, WCF en SharePoint Services 3.0. Volgens de laatste cijfers van Microsoft werken wereldwijd meer dan 8000 klanten met BizTalk Server. Ook binnen ons land werkt een behoorlijk aantal bedrijven met BizTalk Server zoals SNS Reaal, Grontmij, De Friesland Zorgverzekeraar, UWV, Achmea, Interpolis en Stegeman. Dit is allemaal indrukwekkend, maar waar moet de klant nu rekening mee houden op moment dat het BizTalk Server in huis haalt? Waar bestaat het uit en hoe ziet een BizTalk Platform architectuur er nu uit? En tot slot, wat betekent het voor ontwikkelaars, information workers (business analisten) en architecten? In dit artikel zullen deze vragen beantwoord worden. Business context Wat zou de business (de klant) of IT bewegen tot de aanschaf van producten zoals BizTalk? Welke overwegingen of wensen leiden tot oplossingen waar BizTalk een rol in kan spelen? De business vandaag de dag moet flexibel zijn en snel op veranderingen in de markt kunnen inspelen. Het IT deel van een organisatie zal daarbij de eisen van de business volledig moeten begrijpen en kunnen inschatten. Daarnaast moet worden ingeschat, hoe deze business eisen impact hebben op de bestaande of nieuwe systemen. De meest voorkomende business eisen zijn collaboration, governance, ROI en de eerder genoemde flexibiliteit. Collaboration, ofwel samenwerking, binnen en buiten een organisatie, is belangrijk om de business te laten groeien. Dit geldt voor zowel het ad hoc versturen van berichten naar derde partijen, als voor de integratie van alle business functionaliteit binnen de organisatie. Governance, ofwel toezicht, heeft de business nodig om te kunnen voorzien in audit trails ten behoeve van wet- en regelgeving. En ten slotte wil de business altijd een return of investment (ROI) zien, zodat zij inzichtelijk hebben welke voordelen een systeem op termijn heeft. Waarom BizTalk? De hierboven genoemde eisen zijn allemaal legitieme business eisen, maar hoe past BizTalk hier nu in? BizTalk Server biedt de mogelijkheid om bijvoorbeeld legacy systemen te ontsluiten, systemen die voor de business onmisbaar zijn. Vaak zijn dit monolithische, starre systemen, die met behulp van BizTalk flexibeler en wendbaarder worden. Functionaliteit van dergelijke systemen wordt inzichtelijker en makkelijker onderhoudbaar. Dit is niet het enige argument om BizTalk in te zetten. Om het nog meer inzichtelijk te maken wat de argumenten kunnen zijn voor zowel business als de IT, volgt hieronder een overzicht van functionaliteiten Validatie van berichten. De structuur van een te ontvangen en/of te verzenden bericht wordt geanalyseerd en gevalideerd tegen een gedefinieerde structuur. De structuur wordt vastgelegd door middel van schema's.
Transformatie van berichten. In de ideale wereld wordt er van één berichtdefinitie gebruik gemaakt. Meestal is dit echter niet zo en worden voor eenzelfde type object meerdere berichtdefinities gebruikt (schema's). De integratie-broker biedt standaard voorzieningen om berichten om te zetten naar een andere structuur (mapping van het ene schema naar het andere schema). Routering van berichten. Een ontvangen bericht wordt geanalyseerd en op basis daarvan doorgestuurd naar de geabonneerde applicaties/services. Routering kan plaatsvinden op basis van zogenaamde header-informatie of op berichtinhoud (content based routing). Aggregatie van berichten. Het samenvoegen van meerdere berichten tot een nieuw bericht (aggregator pattern). Naast dit pattern zijn er met de integratie-broker meerdere patterns te implementeren, zoals opspliten van berichten (splitter pattern), verzamelen van berichten en versturen naar meerdere ontvangers (gatter-scatter pattern). Betrouwbaarheid en beveiliging. Technisch-functionele diensten zoals gegarandeerde berichtaflevering, beveiligd transport, garantie van afzender en van oorspronkelijkheid van het bericht. Adapters en transport. Koppeling en communicatie naar applicaties en systemen. Orchestration (samenstelling). De mogelijkheid om bestaande diensten te combineren tot een nieuwe, gecombineerde dienst. Een voorbeeld hiervan is het aanbieden van locatie gebaseerde informatie: de service die de locatie bepaalt, wordt gekoppeld aan de service die informatie over de locatie oplevert. Door combinatie van twee 'eenvoudige' services ontstaat een nieuwe service/product. BAM (Business Activity Monitoring). Processen en/of samengestelde services kunnen door middel van voorafgestelde KPI's gemeten worden. BAM is de service die dit realiseert en biedt de mogelijkheid om via een cockpit de resultaten te analyseren en op
magazine voor software development 67
ARCHITECTURE
basis van deze analyse het proces aan te passen. Business to Business Integration. XML, EDI en dergelijke formaten worden ondersteund, zodat externe systemen kunnen communiceren met BizTalk. Ook kunnen Business processen in de ene organisatie communiceren met BizTalk processen in een andere organisatie. RFID Platform. Het platform binnen BizTalk voor ontwikkelen, uitrollen en management van Radio Frequency Identification (RFID) oplossingen, waarbij de meeste RFID apparaten worden ondersteund. Business Rule. Het toepassen van logica (regels) op processen (orchestrations) binnen BizTalk. Het raamwerk maakt het mogelijk declaratieve regels op te stellen, die een duidelijke betekenis hebben en gekoppeld kunnen worden aan feiten (zoals XML documenten, database tabellen of .NET componenten). Management. Management van processen, berichten en applicatie artefacten via een centraal management console. Integratie met Visual Studio. Tools voor ontwikkelen, testen en uitrollen van BizTalk artefacten en oplossingen.
Fig. 1: Schematische weergave van functionaliteiten binnen BizTalk Server 2006 R2 Nu we de business kant van het verhaal belicht hebben, gaan we het eens bekijken vanuit de IT kant. Daarbij kun je de vraag stellen hoe BizTalk past in hun landschap? Het antwoord is deels terug te vinden in de genoemde functionaliteiten van BizTalk. Adapters en transport zijn b.v. interessant, omdat daarmee koppelingen en communicatie naar andere applicaties en systemen mogelijk zijn.
Binnen heterogene omgevingen kan BizTalk met haar adapters een belangrijke rol spelen om applicaties met elkaar te integreren Adapters Adapters die met BizTalk meegeleverd worden of beschikbaar zijn via Microsoft, stellen je in staat met diverse systemen te integreren zoals PeopleSoft, JD Edwards, SAP, Siebel, TIBCO, IBM Mainframes, IBM DB2 SOFTWARE en Oracle. Daarbij worden allerlei communicatieprotocollen DEVELOPMENT NETWORK ondersteund zoals HTTP, FTP, SOAP, MQSeries, MSMQ. Data in de diverse systemen kunnen dus via allerlei communicatie protocollen
FULL-COLOUR
50 cyaan 100 geel
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
68 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
door BizTalk ontsloten worden. Binnen heterogene omgevingen kan BizTalk met haar adapters een belangrijke rol spelen om applicaties met elkaar te integreren. Zeker wanneer binnen een organisatie Microsoft de voorkeur heeft boven leveranciers als IBM of Oracle, die vergelijkbare producten leveren zoals IBM WebSphere en Oracle Fusion. Orchestration Dit is zeker niet de enige interessante functionaliteit van BizTalk. Het kunnen ondersteunen van een zakelijk proces door meerdere diensten samen te stellen met behulp van Orchestration is een erg krachtige functionaliteit. Met behulp van het volgende denkbeeldige scenario wordt dit een stuk duidelijker. In het voorbeeld zullen ook de overige functionaliteiten van BizTalk de revue passeren. BizTalk zal zeer geschikt blijken te zijn voor geautomatiseerde machine naar machine processen. Een landelijk garage bedrijf stelt automobilisten in staat een afspraak te maken voor een servicebeurt via een website. De gebruiker kan, waar dan ook in Nederland, op een gewenst moment deze servicebeurt voor zijn auto laten plaatsvinden. Hij of zij zal via de website een verzoek doen voor een afspraak op een bepaald tijdstip bij een van de aangesloten garages. Een verzoekbericht van de afspraak (persoon, auto, tijdstip, locatie en dergelijke) zal worden verstuurd richting BizTalk Server, die het bericht inspecteert en valideert en vervolgens een proces start. Binnen het proces wordt het bericht verder ontleed en worden andere systemen geraadpleegd. We gaan even uit van een heterogene omgeving, waarbij een CRM applicatie (Siebel) aanwezig is en Oracle en DB2 databases. In dit scenario brengen de adapters van BizTalk uitkomst; immers, koppelingen met deze systemen worden ondersteund. Deze systemen krijgen het verzoekbericht toegestuurd, waar de informatie van het oorspronkelijke verzoekbericht in staat. Deze berichten worden binnen BizTalk aangemaakt, eventueel met behulp van een transformatie, en door routing worden de berichten naar de juiste systemen verstuurd. De systemen sturen hun antwoorden terug naar BizTalk. Deze antwoorden bepalen dan het verdere verloop van het proces. Uiteindelijk zal een succesvol verlopen proces leiden tot een afspraak. Deze afspraak zal dan aangemaakt worden in het systeem van de gewenste garage. Tot slot zal het proces eindigen in een bericht met tijdstip en locatie en deze informatie zal worden verzonden naar de website. De gebruiker zal dit als een bevestiging van zijn verzoek op de website zien. Het bovenstaande is een scenario als een proces succesvol verloopt. Dit is in de praktijk natuurlijk niet altijd het geval. De gebruiker zou dan een melding krijgen, dat de afspraak niet mogelijk is of dat er alternatieven zijn voor tijd en/of locatie. Omwille van de eenvoud hebben we dit buiten beschouwing gehouden. BizTalk Server Overzicht Duidelijk is wel welke functionaliteiten BizTalk Server bevat en wat het een organisatie te bieden heeft. Als architect, ontwerper en developer heb je ook behoefte de exacte werking van BizTalk te kennen en te weten uit welke componenten BizTalk bestaat en wat er onder de motorkap uitzit. BizTalk transformeert en persisteert alle berichten die het ontvangt, in een 'MessageBox' database op SQL Server om te kunnen voorzien in integratie van applicaties (applicatie-integratie) en coördinatie van logica tussen zakelijke processen (Business Process Management). Door middel van een adapter accepteert het berichten van externe applicaties, services, processen en systemen. BizTalk maakt daarnaast gebruik van een pijplijn aan de ontvangende kant om berichten te converteren naar XML Data. Daarna verwerkt BizTalk Server de berichten door middel van een Orchestration en routeert het verzoekbericht (request), of er wordt gebruikt gemaakt van een versturende pijplijn om XML data te converteren naar een ander formaat en door te sturen naar externe applicaties, diensten, processen en systemen.
ARCHITECTURE
Bovenstaande beschrijft in hoofdlijnen de belangrijkste functies binnen BizTalk 2006. Alle data van en naar externe systemen via BizTalk zal de hierboven beschreven weg bewandelen (zie figuur 2). Dit is van wezenlijk belang bij het opzetten van een architectuur voor een BizTalk omgeving. Je kunt je namelijk richten op applicatie integratie en/of proces sturing binnen een organisatie. Belangrijk daarbij is het kunnen doorgronden van BizTalk Server Messaging Architectuur.
componenten, die in volgorde worden uitgevoerd, die voorzien in een specifiek deel van de voorbereiding van een bericht voor publicatie zoals decryptie, ‘parsing’ of validatie. Verzendpoorten Een verzendende poort is een combinatie van een versturende pijplijn (send pipeline) en een zend-adapter. Het is eigenlijk het omgekeerde van een ontvangende poort en haar ontvangst-locatie. Een verzendende poort kan ook nog worden gegroepeerd, zodat die de werking heeft zoals een e-mail distributielijst. De pijplijn wordt gebruikt om een bericht voor te bereiden en te versturen naar een andere service. De zend-adapter is verantwoordelijk voor het uiteindelijke versturen van het bericht en maakt daarbij gebruik van een protocol zoals SOAP of FTP. Orchestrations Orkestratie (orchestration) is een vormgegeven proces binnen BizTalk dat berichten ontvangt vanuit de 'MessageBox' welke voor hem bestemd zijn. Een orkestratie kan ook berichten publiceren door deze te versturen naar de 'MessageBox'. Een bericht verstuurd vanuit een orkestratie, wordt op een gelijke wijze in de 'MessageBox' geplaatst alsof het via een ontvangst-locatie wordt ontvangen.
Fig. 2: Berichten stroom in/uitgaand BizTalk Server Messaging Architectuur De onderstaande figuur geeft een overzicht van een BizTalk architectuur vanuit een bericht (message) perspectief.
Fig. 3: BizTalk messaging architectuur Een bericht wordt ontvangen door een ontvang (receive) locatie in een ontvangende poort. De volgende componenten zijn betrokken in het ontvangen, verwerken en versturen van berichten binnen BizTalk Server. Ontvangende poorten en ontvangst locaties Een ontvangende (receive) poort is een verzameling van een of meerdere ontvang-locaties, die de specifieke ingangen in BizTalk definiëren. Een ontvangst-locatie is de configuratie van een enkel eindpunt om berichten te kunnen ontvangen. De locatie bevat configuratie-informatie van zowel ontvangst-adapter als -pijplijn (pipeline). De adapter is verantwoordelijk voor het transport en het communicatiedeel van het ontvangen van een bericht. De pijplijn is verantwoordelijk voor voorbereiding van een bericht, voordat het in de ‘MessageBox’ kan worden geplaatst (gepubliceerd). Een pijplijn bevat een reeks
Berichten ‘brievenbus’ (MessageBox) Database Het hart van de ‘publish/subscribe’ engine in BizTalk is de ‘MessageBox’ database. Deze bestaat uit twee componenten, nl. een of meerdere Microsoft SQL Server databases en een berichten-agent. De SQL Server database voorziet in persistentie van zaken als berichten, haar eigenschappen, de abonnementen hierop, de status van orkestraties, de volgdata (tracking) en de host wachtrijen (queues) voor routering. Host en host instanties Een host is een logische representatie van een Microsoft Windows proces, die BizTalk Server artefacten als zend-poorten en orkestraties uitvoert. Een host instantie (host instance) is een fysieke representatie van een host op een specifieke server. Een host kan een ‘in-process’ host zijn, die wordt beheerd door BizTalk Server, of een geïsoleerde (isolated) host. In het laatste geval voert BizTalk Server de programmeercode in een niet door BizTalk gecontroleerd proces uit, b.v. via Internet Information Server (IIS) voor HTTP en SOAP. Hosts worden gedefinieerd voor een gehele BizTalk Server groep, wat een verzameling van BizTalk Servers kan zijn. Een BizTalk Server groep deelt de configuratie van MessageBox(en), poorten, etc. Deze componenten en hun gedrag/rol binnen BizTalk Server zijn van invloed op de wijze waarop de architectuur voor een BizTalk omgeving tot stand komt. Een belangrijke architectuurafweging richt zich op het wel of niet kunnen verwerken van berichten. Daarbij spelen volume en frequentie van berichten van elk interface dat de BizTalk omgeving gaat ondersteunen, een rol. Andere overwegingen spelen ook een rol bij een architectuur voor BizTalk. Waar worden b.v. de poorten (ontvang/verzend) ondergebracht; samen op een machine met eventueel Orchestrations? Worden de berichten apart verwerkt of bewerkt? Gaan alle componenten draaien op enkele hosts op één enkele machine? Zo ja, dan betekent het dat deze voldoende resources (verwerkingskracht (CPU), geheugen en diskruimte) moet bezitten. Anders kunnen de componenten beter draaien in hosts verspreid over meerdere machines, zodat de belasting van de resources verspreid kan worden. Overwegingen die onder andere ook bij de uitrol van BizTalk Server een belangrijke rol spelen. Daarbij speelt ook de hoeveelheid aan omgevingen een rol zoals een ontwikkel-, test-, acceptatie- en productie-omgeving. Dit valt buiten de scope van dit artikel. BizTalk Server Platform Architectuur De functionaliteiten van BizTalk Server en haar werking zijn nu
magazine voor software development 69
ARCHITECTURE
uitgebreid aan bod gekomen. Daarbij hebben we nog niet de diverse rollen die met BizTalk te maken kunnen hebben behandeld. Gezien de uitgebreide mogelijkheden van BizTalk Server en de wijze waarop het binnen een organisatie kan worden ingezet kun je BizTalk eigenlijk niet meer beschouwen als een product op zich. Het is wel een server product van Microsoft, maar feitelijk zou je moeten spreken over een platform, waarbij een architect, ontwikkelaar en information worker (business analist) een wezenlijke rol spelen (figuur 4).
BizTalk is meer een Platform dan een Server
Fig. 4: Schematische weergave van BizTalk Server Platform De architect zal zich richten op de architectuur van BizTalk Server zoals deze in dit artikel aan bod is gekomen. De developer zal zich bezighouden met ontwikkelen van BizTalk artefacten zoals pipelines, schemas, mappings en orchestrations. De administrator zal zich focussen op uitrol en beheer van de BizTalk-omgeving en -applicaties. Tot slot de information worker, die zich deels met ontwerp van schema’s zal bezighouden. Dat is echter niet het enige dat hij of zij zal doen: • Opstellen van logica die van toepassing is op een zakelijk proces binnen BizTalk via een orkestratie; • Monitoren of inzicht verkrijgen in lopende processen; • Generen van rapportage rond berichtenverkeer en processen. De rollen van administrator, developer en analist zijn gebonden aan tools zoals de management console van BizTalk, Visual Studio 2005 en de Business Intelligence Manager (Business Rule Composer). BizTalk versies Tot slot is het voor architect en wellicht developer belangrijk om te weten, dat er een keuze bestaat uit 3 BizTalk versies: Branche, Standard en Enterprise. De Branche-versie is bedoeld als extra installatiepunt bij een businesspartner naast een Enterprise-editie bij de hoofdgebruiker. De Standard-versie voorziet alleen in een single-server installatie met de mogelijkheid om de database op een aparte server in te stellen. De standaard versie is niet schaalbaar en biedt slechts ruimte aan 5 applicaties. Als er een omgeving ingericht gaat worden die ruimte moet bieden aan een groeiend aantal applicaties, dan is de functionaliteit van de Enterprise-versie vereist. Deze versies zijn beschikbaar bij BizTalk Server 2006 R2. Wat betreft BizTalk Server 2006 heeft R2 heeft de volgende relevante uitbreidingen / verbeteringen ten opzichte van BizTalk Server 2006: SOFTWARE DEVELOPMENT NETWORK • Ondersteuning voor Windows Communication Foundation; • Ondersteuning voor Windows SharePoint Service 3.0 en MOSS;
FULL-COLOUR
50 cyaan 100 geel
• Ondersteuning voor Excel 2007 voor Business Activity Monitoring. Het laatste is ook niet onbelangrijk als een afweging gemaakt dient te worden bij migratie van eerdere versies van BizTalk Server naar R2. De Toekomst Met de komende Biztalk 2009 zullen de mogelijkheden van de nieuwe versies van Windows producten als Windows Server 2008, .NET Framework 3.5, Visual Studio 2008 en SQL Server 2008 gebruikt gaan worden. Daarnaast zullen naar alle waarschijnlijkheid de volgende mogelijkheden aan het product worden toegevoegd: • Ondersteuning voor UDDI 3.0; • Verbeterde en nieuwe adapters voor LOB applicaties, databases en legacy/host systemen; • RFID Mobile; • Verbeterde interoperabiliteit en connectiviteit voor protocollen zoals SWIFT en EDI; • ESB Guidance 2.0; • SOA patterns en practices guidance. De toekomstige versie BizTalk 2009 laat nog even op zich wachten, terwijl vele BizTalk specialisten graag met BizTalk en Visual Studio 2008 aan de gang willen. BizTalk 2009 zal BizTalk verder richting een platform duwen voor implementatie van een SOA en/of ESB. Daarmee zal het product meer aan populariteit winnen en het aandeel in de markt van applicatie-servers doen toenemen. Conclusie In dit artikel is duidelijk geworden wat BizTalk Server betekent voor zowel de business-kant als de IT-kant binnen een organisatie. Daarbij zijn de afwegingen die kunnen worden gemaakt of gelden eveneens beschreven. Tevens is inzichtelijk gemaakt welke functionaliteiten BizTalk Server kent, uit welke componenten het bestaat en hoe de werking intern is. Door te wijzen op het feit dat BizTalk Server meer als een platform kan worden beschouwd dan een product, zijn de diverse rollen als architect, administrator, information worker en developer beter te duiden. Tot slot zijn de rollen gekoppeld aan de beschikbare tooling en de diverse versies van BizTalk aan bod gekomen. Interessant is de ontwikkeling die het product door heeft gemaakt en hoe de toekomstige versies eruit gaan zien. •
Steef-Jan Wiggers Steef-Jan Wiggers is werkzaam als Information Architect bij Inter Access. Hij heeft een aantal jaren gewerkt als software ontwikkelaar en database administrator bij diverse bedrijven. Sinds 2004 is Steef-Jan bezig met BizTalk Server en richt hij zich op architectuur, integratievraagstukken, analyse en ontwerp. Via zijn blog http://soathoughts.blogspot.com/ kun je zijn verrichtingen en ervaringen lezen. Voor vragen is hij bereikbaar via [email protected]
.Net
TIP:
DataGrid voor WPF
50 zwart 15 cyaan
SOFTWARE DEVELOPMENT NETWORK BEELDMERK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK
SOFTWARE DEVELOPMENT NETWORK DIVISIES
CONFERENCE SOFTWARE DEVELOPMENT NETWORK
70 SOFTWARE DEVELOPMENT NETWORK
MAGAZINE EVENTS
Microsoft heeft een preview DataGrid voor Windows Presentation Foundation beschikbaar die van http://www.codeplex.com/ wpf te downloaden is.
Advertentie Sybase iAnywhere / Elmo ICT Systems
Advertentie Furore