SOFTWARE DEVELOPMENT NETWORK
MAGAZINE 6 & 7 OKTOBER A.S. SDN CONFERENCE Conferentiecentrum Leeuwenhorst Noordwijkerhout
SCHRIJF NU IN!
IN DIT NUMMER O.A.: C#3.0: Van Delegate naar Lambda < First Look at Advantage Database Server 9 < Silverlight2: MSN Video ListBox < Delphi for .NET: Writing User-Defined Functions for Blackfish Databases < Data Centric Services met ADO.NET Data Services <
Nummer 98 Augustus 2008 SDN Magazine verschijnt elk kwartaal en is een uitgave van Software Development Network
98
www.sdn.nl
Advertentie Sybase iAnywhere / Elmo ICT Systems
Colofon Uitgave:
voorwoord
Software Development Network Zeventiende jaargang No. 98 • augustus 2008
Bestuur van SDN: Remi Caron, voorzitter Rob Suurland, penningmeester Joop Pecht, secretaris Mark Vroom, vice-voorzitter
En … was het schrikken? Of toch de gehoopte, aangename verrassing, dat nieuwe uiterlijk van het SDN-magazine? We hebben de indruk dat de nieuwe layout in het algemeen zeker als een vooruitgang wordt ervaren, en dat doet ons deugd natuurlijk. We hebben geprobeerd om in dit magazine een aantal verbeterpunten door te voeren, zoals de manier waarop de tips worden weergegeven - die te veel uitstraalden alsof ze bij het nevenstaande artikel behoorden - en de manier waarop code-listings worden weergegeven. Het laatste woord zal er nog niet over zijn gesproken, maar we hopen zo op een agile manier tot 98% tevredenheid te komen, want 100% zal vast een utopie blijken.
Redactie: Rob Willemsen (
[email protected])
Aan dit magazine werd meegewerkt door: Maurice de Beijer, Mark Blomsma, Remi Caron, Leon Carpay, 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 Sybase iAnywhere/ Elmo ICT Systems Sira Holding b.v. Aladdin Logica Giraffe 4DotNet b.v. Bergler Nederland b.v. Avanade iAnywhere Solutions/ a Sybase company Furore
2 16 19 20 27 32 42 48 63 64
Adverteren? Informatie over adverteren en de advertentietarieven kunt u vinden op www.sdn.nl onder de rubriek Magazine.
Een van de inhoudelijke vernieuwingen die ingezet is binnen het SDN, te weten van Developer naar Development, krijgt in dit magazine zijn gestalte. De nieuwe networks, of tracks, of secties of hoe je ze ook wilt noemen, zijn vertegenwoordigd. Robertjan Tuit doet voor het User eXperience network de aftrap met “Silverlight2 MSN Video ListBox“ waarin hij laat zien dat desktop en browser steeds dichter bij elkaar komen, inclusief realtime data-binding. Hij ontkomt er vanuit de UX-achtergrond natuurlijk niet aan om ook stil te staan bij templating en styling, maar presenteert zijn informatie nog in behoorlijke mate in de richting van de developer. Misschien om de overgang voor de reguliere SDN-er niet te plots te maken. Databases is de naam van het 2e nieuwe network dat vanaf nu in het SDN-magazine vertegenwoordigd is. Het artikel van Cary Jensen & Loy Anderson, “First Look at Advantage Database Server 9”, gaat hier natuurlijk heel specifiek over, dat mag duidelijk zijn. Maar ook de bijdrage van Holger Flick, “Delphi for .NET: Writing User-Defined Functions for Blackfish Databases” heeft, zoals de titel duidelijk maakt, een link met databases. Zo wordt eens te meer geïllustreerd dat de tijden achter ons liggen dat een developer het zich kon veroorloven zich enkel te focussen op zijn eigen kunstje, al is het verkleinwoord een understatement voor wat men binnen 1 omgeving kan/kon presteren. En deze constatering geldt al evenzeer voor designers en architecten, wat onderstreept wordt door het eerder genoemde artikel van Robertjan Tuit (voor designer en developer), maar ook door de bijdrage van het 3e nieuwe network, Architecture, in deze editie van het SDN-magazine, nl. “Data Centric Services met ADO.NET Data Services” van de hand van Roy Cornelissen, ook al zo’n cross-over tussen 2 aandachtsgebieden. Daarnaast houden we gelukkig nog heel veel interessants over van de networks die we al (wat) lang(er) koesteren binnen het SDN. Maar ook daar zie je steeds meer mixen ontstaan. Lezen over Delegates en Lambda Expressies (zie het artikel van Mark Blomsma) is net zo goed interessant voor C#-ers als voor VB-ers als voor Delphi-ers … en zou eigenlijk verplichte leesstof moeten zijn voor alle professionele en serieuze developers. Ook al kun je wellicht het gepresenteerde niet direct toepassen in je eigen werkomgeving - kun je dat overigens zo noemen, ‘je werkomgeving’, want dat suggereert toch een beetje dat er maar 1 omgeving is? - het kennis nemen van concepten in andere omgevingen is altijd nuttig, tenminste als je zelf bij wil blijven bij wat er om je heen gebeurt. En dat zelfde geldt voor de manier waarop je je software documenteert (zie het artikel van Stephan Smetsers) en zo kan/moet ik eigenlijk de overige artikelen in dit rijtje opnemen, omdat zij alle hun eigen bijdrage kunnen hebben om je kennis en inzichten in design & development van software uit te breiden. Kijk dus over de schutting! Op het moment dat dit magazine je brievenbus binnenglijdt, klopt het volgende SDN-event ook al aan bij je voordeur: de SDN Conference van 2008 - dat event dat we vorig jaar nog SDC 2007 noemden. Maar als je door dit blad heen bladert, zal je moeilijk kunnen ontgaan dat het toch echt gaat gebeuren, en wel op 6 & 7 oktober. Dus zorg dat je erbij bent! Kunnen we het meteen ook even van persoon tot persoon hebben over wat je nog meer of anders zou willen zien in dit vernieuwde SDN-magazine. En misschien heb je wel een briljant idee voor de 100e uitgave van het blad dat volgend jaar op de rol staat… Veel & verfrissend leesplezier! Rob Willemsen,
[email protected] •
magazine voor software development 3
Inhoud 03
Voorwoord
Rob Willemsen
04 05
Inhoudsopgave C# 3.0 Van Delegate naar Lambda
Mark Blomsma
09
Een Eigen Gridje Is zo Eenvoudig nog Niet
Marcel van Kalken
17
Softwaredocumentatie met Sandcastle
Stephan Smetsers
21
DotNetNuke Maintenance Power Tips
Lee Sykes
24
IEnumerable, de basis voor LINQ
Mendelt Siebenga
28
First Look at Advantage Database Server 9
Cary Jensen & Loy Anderson
34
SDN Conference: overzicht sprekers en sessies
36
Interesting Things: Core
Sander Hoogendoorn
37
Silverlight2 MSN Video ListBox
Sander Hoogendoorn
SDN CONFERENCE 2 DAYS SOFTWARE DEVELOPER CONFERENCE
41 43
Agenda 2008 VSTO 3.0 en Word Content Controls
Maarten van Stam
49
ASP.NET onder de Motorkap: Het Provider Model Nader Bekeken
Michiel van Otegem
50
NAV+VSTO+WPF+LINQ = OBA
Marianne van Wanrooij
55
Data Centric Services met ADO.NET Data Services
Roy Cornelissen
6 & 7 OKTOBER 2008 CONFERENCE.SDN.NL
60
Delphi for .NET: Writing User-Defined Functions for Blackfish Databases
Holger Flick
.NET
VISUAL C#
Mark Blomsma
C#3.0: Van Delegate naar Lambda C# is een taal waar delegates een belangrijke rol spelen. Events zijn een bijzondere implementatie van delegates. In C# 2.0 zijn anonymous delegates ofwel anonymous methods geïntroduceerd en met de komst van C# 3.0 is er weer iets nieuws: Lambda expressies. Lambda expressies zijn een nieuwe notatie voor delegates. Al met al is het dus belangrijk om goed te begrijpen wat delegates zijn en doen om te begrijpen hoe Lambda expressies werken. In dit artikel zullen we kijken naar de evolutie van mogelijkheden en syntax als het gaat om delegates in C#. Een wereld zonder delegates Je kunt een hoop bereiken zonder delegates. Listing 1 laat zien hoe uit een lijst met 9 nummers alleen de even nummers worden geselecteerd. Het resultaat wordt vervolgens middels databinding in een listbox getoond (de volledige code is te downloaden op de SDN site).
Een delegate declaratie is de beschrijving van de signatuur van een methode
List
_numbers = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
soortgelijke toepassing ook werkt met een List van klanten, of een DataTable met klanten.
private void btnNoDelegates_Click (object sender, EventArgs e) { List result = GetEvenNumbers( _numbers ); this.lstResult.DataSource = result; } private List GetEvenNumbers( List numbers ) {
Het is niet onwaarschijnlijk dat je in staat wilt zijn om te kunnen filteren op verschillende criteria. Bijvoorbeeld: geef me alle oneven nummers, of alle nummer deelbaar door 3. Of als het gaat om klantobjecten: je wilt b.v. niet (alleen) filteren op klantnaam, maar ook op plaats of klantnummer. Met de aanpak van listing 1 betekent dit meerdere methodes die allemaal erg veel op elkaar lijken. We willen dus eigenlijk een generieke selecteerfunctie maken en aan de selecteerfunctie een parameter meegeven. Maar wat moet die parameter dan zijn?
List result = new List(); foreach ( int i in numbers ) { if ( i % 2 == 0 ) { // if i matches the filter // then add it to the result result.Add( i );
Wat is een delegate? Wikipedia definieert een delegate als volgt (excuus voor het Engels, maar er is geen Nederlandse definitie): • A delegate is a form of type-safe function pointer used by the .NET Framework. Delegates specify a method to call and optionally an object to call the method on. They are used, among other things, to implement callbacks and event listeners.
} } return result; }
Listing 1: Filteren zonder delegates De code werkt en hoewel er in dit voorbeeld wordt gewerkt met een generic List met integers, kun je je denk ik wel inbeelden dat een
Mijn persoonlijke definitie is: • Een delegate declaratie is de beschrijving van de signatuur van een methode. De signatuur van een methode bestaat uit de return value en de typen van de parameters, inclusief de volgorde van de parameters. Voor mij helpt het om te denken dat een delegate een interfacebeschrijving is van een enkele methode. Een interface beschrijft welke methodes,
magazine voor software development 5
.NET VISUAL C# events en properties een class moet implementeren. Je kunt een interface gebruiken als type voor een parameter of een return type. Op een zelfde manier definieert een delegate wat het return type van een methode moet zijn en wat de parameters, typen en volgorde van parameters van een methode moeten zijn.
/// filter criteria and needs to be included in /// the result. /// delegate bool Filter( int x ); private List Select ( List numbers, Filter filter )
// delegate print matches any method // that returns void and takes one
{
// paramter of type string
List result = new List();
delegate void Print(string s);
foreach ( int i in numbers ) {
class WhatIsADelegate
// invoke the delegate to see if
{
// the number needs to be part of the result // matches delegate Print
if ( filter( i ) == true )
public void PrintToPaper( string text )
{
{
// if i matches the filter
}
// then add it to the result result.Add( i ); }
// matches delegate Print }
private void PrintToConsole( string t )
return result;
{ }
} // matches delegate Print
Listing 3: Select met delegate
internal void DumpToLog( string s ) { } // no match public void DumpToLog( string s, int logLevel ) {
In Listing 3 zien we dat de ‘Select’ methode nu een resultaat levert zonder zelf te weten wat de filtercriteria zijn. In C# 3.0 is het werken met delegates vereenvoudigd zodat je zeer eenvoudig de delegate kunt aanroepen. In listing 3 zie je dat we de ‘filter’ variabele gebruiken alsof het een methode is. Voorheen was het noodzakelijk om ‘filter.Invoke(i)’ te doen.
} // no match internal string FormatToUpper( string s ) { return s.ToUpper(); }
In C# 3.0 is het werken met delegates vereenvoudigd zodat je zeer eenvoudig de delegate kunt aanroepen
}
Listing 2: Voorbeeld delegates In listing 2 zien we declaratie van de delegate ‘Print’. Deze delegate matcht met elke methode die ‘void’ als return type heeft en een enkele string als parameter accepteert. De methodes ‘PrintToPaper’, ‘PrintToConsole’ en ‘DumpToLog’ matchen allemaal met de ‘Print’ delegate, ongeacht de toegankelijkheid (public, internal, private) van de methode. De overload op ‘Dump ToLog’ met twee parameters is geen match en ‘FormatToUpper’ heeft het verkeerde return type en is dus ook geen match.
We kunnen nu methodes gaan schrijven die we als filter meegeven aan de Select-methode. /// <summary> /// Return true if the number is even. /// private bool IsEvenNumber( int number ) { return ( number % 2 == 0 ); } /// <summary>
Ok, we kunnen dus een delegate declareren en we weten welke methodes een match zijn met een delegate, maar wat kun je er dan mee? Terug naar het oorspronkelijke probleem: selecteren en filteren.
/// Return true if the number is odd. /// private bool IsOddNumber( int number ) {
Filteren met delegates We willen de GetEvenNumbers methode vervangen door een meer generieke methode ‘Select’. Zoals eerder gezegd, een delegate kan gebruikt worden als een parameter type, dus aan de Select-methode geven we de list met nummers mee en als tweede parameter een delegate gedeclareerd als ‘Filter’.
return ( number % 2 != 0 ); } private void btnDelegateIsEvenNumber_Click ( object sender, EventArgs e ) { // IsEvenNumber is a method matching the
/// <summary>
// Filter delegate
/// The filter delegate takes an integer as a parameter
List result = Select( _numbers, IsEvenNumber ); this.lstResult.DataSource = result;
/// and returns a boolean value. /// The return value is TRUE if the integer matches the
6
MAGAZINE
}
.NET VISUAL C# private void btnDelegateIsOddNumber_Click
private void btnLambda1_Click
( object sender, EventArgs e ) {
( object sender, EventArgs e ) {
// IsOddNumber is a method matching the
// create delegate using Lambda expression
// Filter delegate
Filter evenNumbers = number => ( number % 2 == 0 );
List result = Select( _numbers, IsOddNumber );
List result = Select( _numbers, evenNumbers );
this.lstResult.DataSource = result;
this.lstResult.DataSource = result;
}
}
Listing 4: Filteren met delegates
private void btnLambda2_Click
In listing 4 zien we dat de IsEvenNumber en IsOddNumber beide een boolean waarde teruggeven en een integer als parameter accepteren, met andere woorden, ze zijn een match met de Filter delegate en kunnen als parameter meegegeven worden aan de Select-methode. Ook hier zien we vereenvoudiging in de syntax: je kunt gewoon de naam van de methode als parameter meegeven, een beetje alsof het een reference naar de methode is. Voorheen had je het volgende in moeten intikken:
{
( object sender, EventArgs e )
List result = Select( _numbers, new Filter(IsEvenNumber) );
Dit is nog steeds wat er gebeurt. De compiler creëert nog steeds een delegate object, je hoeft alleen minder te tikken. Anonymous methods: De methode zonder naam - 1 In C# 2.0 is een feature toegevoegd die ‘anonymous methods’ genoemd wordt. Voor C# 2.0 was de enige manier om een delegate te declareren om een ‘named method’ ofwel een methode met naam te gebruiken, zoals we hebben gezien in listing 4. Een anonieme methode is een block met code die inline gedefinieerd wordt en als delegate parameter wordt meegegeven. private void btnAnonymousDelegateEvenNumbers_Click ( object sender, EventArgs e ) {
// use Lambda expression inline List result = Select( _numbers, x => ( x % 2 == 0 ) ); this.lstResult.DataSource = result; }
Listing 6: Lambda expressies vervangen anonieme methodes Listing 6 laat zien dat we een Lambda expressie kunnen toekennen aan een delegate variabele en als dat kan, dan kunnen we natuurlijk ook een Lambda expressie inline in een methode aanroep plaatsen. Dus wat gebeurt er precies? number => ( number % 2 == 0 )
We geven met deze Lambda aan dat er een input variabele is genaamd ‘number’. We doen een modulo 2 op deze parameter en vergelijken het resultaat met nul. Merk op dat er geen return statement is. Een Lambda bevat meestal maar 1 regel code, maar kan er meer hebben. Omdat onze Lambda uit maar 1 regel code bestaat hoeven we geen accolades te gebruiken om de scope van het block aan te geven. Tevens hoeven we geen return keyword te gebruiken. De volgende Lambda laat zien dat we het nummer eerst willen ophogen alvorens te delen door 2. We moeten nu het return keyword toepassen.
// create an anonymous method to test for even numbers List result = Select(
number => { number++; return number % 2 == 0; };
_numbers, delegate( int number ) { return ( number % 2 == 0 ); }
Hoewel we nergens parameter types aangeven controleert de compiler wel degelijk op types. Als we de Lambda veranderen in:
); this.lstResult.DataSource = result;
number => ( number % 2 )
}
Listing 5: Anonieme methode als delegate
Dan is de return value ineens van het type integer in plaats van boolean. De compiler geeft nu een error om aan te geven dat de Filter delegate een boolean verwacht.
In listing 5 zien we dat we wederom de Select-methode uit listing 3 aanroepen, maar dit maal met als tweede parameter ‘delegate( int number ) { return ( number % 2 == 0 ); }’. Het delegate keyword geeft aan dat we een delegate willen creëren; de delegate method heeft een paramater van het type integer en de body van de methode is een enkele regel code met als resultaat een boolean waarde. Hoewel dit erg effectief is, is het niet erg leesbaar. Aan het begin van dit artikel zei ik al dat Lambda expressies ook delegates zijn. Lambda expressies hebben tot doel dit soort code leesbaarder te maken.
LINQ en Lambdas Lambda expressies zijn een korte en bondige manier om een anonieme method ofwel delegate te definiëren. Ze worden veel gebruikt in de andere nieuwe feature van C# 3.0: LINQ. Nu je snapt wat ze zijn en doen zul je beter begrijpen wat er gebeurt als je een LINQ query uitvoert. Op basis van de code die we nu hebben, hebben we al bijna een mini versie van onze eigen LINQ implementatie gemaakt. public static List Select
Lambda: De methode zonder naam - 2 Om de code in listing 5 beter leesbaar te maken is in C# 3.0 het concept van Lambda expressies geïntroduceerd. Alle Lambda expressies maken gebruik van de nieuwe Lambda operator ‘=>’. Je kunt deze lezen als ‘goes to’ of ‘gaat naar’. De linkerzijde van de Lambda operator bevat de input parameters en de rechterzijde een expressie of een code block. Een Lambda expressie kan toegekend worden aan een delegate type.
( this List numbers, Filter filter ) { List result = new List(); foreach ( int i in numbers ) { // invoke the delegate to see if // the number needs to be part of the result if ( filter( i ) == true )
magazine voor software development 7
.NET VISUAL C# { // if i matches the filter // then add it to the result result.Add( i ); } } return result; }
Listing 7: Select als een extension method Een andere feature van C# 3.0 is extension methods. Met een extension method kun je een method toevoegen aan een bestaande class. Een extension method is altijd static en heeft alleen toegang tot de public methoden en properties van een object. Je maakt een static method tot een extension method door aan de eerste parameter in de parameterlijst het ‘this’ keyword toe te voegen. In listing 7 zien we dat de Select-methode nu een extension is geworden van een willekeurig object van het type ‘List’.
6 & 7 oktober a.s.: SDN Conference 35 sprekers 100 sessies. Schrijf nu in!
Als we dit combineren met de Lambda die we in listing 6 hebben gebouwd krijgen we listing 8. private void btnExtensionMethodFind_Click ( object sender, EventArgs e ) { // use extension method with Lambda List result = _numbers.Select( x => ( x % 2 == 0 ) ); this.lstResult.DataSource = result; }
Listing 8: Select als een extension method Dit ziet er al bijna hetzelfde uit als wat LINQ doet. Natuurlijk is LINQ veel krachtiger omdat het veel generieker is opgezet en niet alleen op ‘List’ werkt (de meeste LINQ extensies werken op IEnumerable), maar het idee is wel hetzelfde. Tot slot We hebben gezien hoe delegates zijn gegroeid van benoemde methodes naar anonieme methodes naar Lambda expressies. Aan het einde van de dag zijn Lambdas niks meer dan een bondige manier van het definiëren van een delegate.Links • The Blomsma Code: http://blog.develop-one.com • Anonymous Methods: http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx • Lambda Expressions: http://msdn.microsoft.com/en-us/library/bb397687.aspx •
Information Worker
TIP:
De snelste manier om SharePoint te laten crashen SharePoint is een heel stabiel en betrouwbaar systeem, ontworpen om (honderden) duizenden users te bedienen, dat onder normale omstandigheden nooit crasht. Toch, als u zich verveelt of wilt laten ontslaan, zet dan een “Page Viewer WebPart” in de Home page van de Portal, en configureer de URL van de WebPart met een pointer naar zich zelf. Het resultaat is een prachtig “Droste effect”, de CPU van de servers loopt onmiddellijk naar 100% en het geheugen wordt langzamerhand gevuld totdat de servers onherroepelijk crashen:
Mark Blomsma Mark Blomsma is een software developer, solutions architect, auteur and spreker gespecializeerd in Microsoft .NET technologieen als C#, WF, WCF, WPF, Silverlight en ASP.NET. Hij heeft meer dan 14 jaar ervaring in de IT en werkt is werkzaam voor zijn eigen bedrijf, Develop-One (www.develop-one.com). Hij heeft 5 Microsoft MVP awards ontvangen voor zijn bijdrage aan de software ontwikkel community.
8
MAGAZINE
NB 1: Probeer het niet in een productie server … NB 2: De enige manier om de schade te repareren is de WebPart direct vanaf de DataBase te verwijderen (Content DataBase, WebParts Table) (Met dank aan Gustavo Velez)
DELPHI
Marcel van Kalken
Een Eigen Gridje Is zo Eenvoudig nog Niet . . . De DevExpress-grid Een paar jaar geleden was ik al eens in de weer geweest met de gridcomponent van DevExpress, en na het downloaden en bekijken van de demo werd besloten dat deze het zou worden. Ik wist nog van de oude versie (3) dat de grid omgeven werd door een werkelijk enorme hoeveelheid properties om de werking en het uiterlijk aan te passen, dus werd meteen besloten om er een eigen (sub)class van te maken, zodat niet elke ontwikkelaar in het team zich hoefde te bemoeien met de eigenschappen en de uiterlijkheden van de grid. Na installatie nestelde de (enorme) hoeveelheid componenten waaruit de grid bestaat zich prima in de IDE van Rad Studio 2007. Even een nieuwe Win32-app gemaakt, en een grid op het scherm gekwakt, en het volgende staat voor je neus:
Een klant van mij vroeg me wat schermen van hun programma op te poetsen door de grid-schermen te voorzien van de mogelijkheid om op kolommen te sorteren. Geen rare gedachte, aangezien dat in de wereld die Windows heet nou eenmaal heel gebruikelijk is. Denk aan Outlook, Excel, etc. Maar omdat de schermen met gridjes opgebouwd waren uit ListViews was deze ombouw nogal een tijdrovende klus. Dus werd besloten om de ListViews, waar veel programmeerwerk in zat, te vervangen door een commerciële grid-component.
cxGrid1Level1: TcxGridLevel; cxGrid1: TcxGrid; private { Private declarations } public { Public declarations } end;
Om te bepalen wat we uiteindelijk met de grid willen bereiken zullen we eerst deze versie maar eens tot leven brengen en kijken wat-ie kan. Er staat vast wel ergens een NorthWind.mdb op de schijf, dus maar even een TAdoConnection , TAdoQuery, en een TDatasource op het form gezet, ConnectionString gemaakt, en de query gevuld met ‘select * from customers’ . En alles met elkaar verbonden, en op connected en active gezet… Da’s gek. Normaal zie je met een TDbGrid dan meteen je data in beeld. Het is ook even zoeken naar het Datasource-property van het cxGrid. Het blijkt een property te zijn van cxGridDbTableView1.Datacontroller. Achteraf ook logisch, aangezien de grid ook ‘unbound’ moet kunnen functioneren. Maar nog steeds geen kolommen in beeld. Hoewel de scroll-bar van de grid me laat zien dat er wel beweging in is gekomen:
Grappig. Het ziet ernaar uit dat de grid uit een samenstelling van componenten bestaat. Even met F12 aan de andere kant kijken bevestigt dit: type TForm1 = class(TForm) cxGrid1DBTableView1: TcxGridDBTableView;
magazine voor software development 9
DELPHI Blijkbaar moet altijd de inhoud van de grid met de hand worden bepaald. Dat is overigens niet zo moeilijk; een dubbelklik op de grid of een klik op ‘Customize’ opent een editor waarin met een knop ‘Retrieve fields’ alle velden uit de dataset kunnen worden opgepikt. Tijd om het oude TDbGrid van het scherm te halen om het cxGrid de ruimte te geven. En even runnen om te zien wat die allemaal standaard in huis heeft, en wat we daarvan willen gaan gebruiken.
paneeltje dat zichtbaar wordt onder de grid, op het moment dat een filter actief is. Erg leuk, maar zonde van de schermruimte. Wel jammer dat, zonder dit paneeltje, niet echt duidelijk is dat er een filter op een bepaalde kolom actief is. Al met al ziet na een paar klikken het scherm er al indrukwekkend uit:
Groeperen Het meest opvallende aan de grid is een paneeltje boven de grid waarin staat ‘Drag a column here to group by that column’. Proberen dus. Het veld (kolom) ‘Country’ leent zich hier wel voor, want daar kun je wel een paar gebruikers blij mee maken, denk ik.
Blijkbaar weten ze bij DevExpress ook wel dat hun grid een scala aan mogelijkheden heeft Als je, zoals je in Outlook gewend bent, op een kolomkop klikt, zie je meteen dat er op de betreffende kolom gesorteerd wordt. Da’s mooi. Net zoals het omgekeerd sorteren, en middels de shift-toets het sorteren op meerdere kolommen. De opgelegde sortering is ook keurig zichtbaar met pijltjes in de kolomkoppen. Alleen jammer dat bij sorteren op meerdere kolommen niet duidelijk is welke kolom nou als eerste (of tweede) is gebruikt.
Nu met een echte database … Toch eens even kijken. We bouwen allemaal programma’s voor klanten die iets meer moeten kunnen dan het weergeven van een testbestandje of een cd-verzameling. Laten we dus hetzelfde traject maar eens doen met een volwassen database, met een kleine 2 miljoen records in één van de tabellen. Zo gezegd, zo gedaan, even wat IBX componenten op het scherm, en de Interbase-database eronder gezet. En dan eens kijken of het nog steeds lekker loopt:
Als je de muis over de kolomkoppen beweegt, zie je een kleine button verschijnen (anders dan die van het sorteren). Erop klikken geeft een lijst met waardes die zich in de onderliggende kolom bevinden, en waarop je dus kunt filteren. Ook erg handig:
Aaaarrrrgggghhhh!. En het ging zo lekker. Tijd om de help er maar eens bij te pakken. En die help is omvangrijk. Blijkbaar weten ze bij DevExpress ook wel dat hun grid een scala aan mogelijkheden heeft, want het help-bestand heeft een omvang die evenredig is met het aantal mogelijkheden van de grid. Net zoals het aantal samples dat wordt meegeleverd. En daarmee houdt het niet op, hun website heeft een support-centre waar het uitstekend zoeken is naar eerdere vragen of problemen die andere gebruikers daar eerder hebben neergelegd. Gelukkig maar, want het blijkt dat het niet allemaal even simpel is wat de makers hebben bedacht.
De ‘custom’ optie geeft een dialoog waarin een samengesteld filter kan worden gebouwd. Ook erg leuk, maar misschien wat too much voor de gemiddelde gebruiker.
Even een korte uitleg over de opbouw van de grid. Zoals uit het eerste probeersel bleek, bestaat de grid uit (tenminste) 3 componenten: een Grid, een Level, en een View. De grid is niet meer dan een omhulsel met wat (weinig) algemene zaken. Het Level is bedoeld om binnen een Grid een ander Grid te kunnen weergeven. Een soort parent-child relatie. En de View is er om de weergave binnen dat Level te bepalen. Er kan worden gekoppeld aan data (bound of unbound), en ingesteld worden hoe de records weergegeven worden, maar behalve het platte ‘grid’-model zijn er nog andere (Cards) weergavemogelijkheden.
Even testen: en ja, dit werkt … alleen gebeurt er niks Later maar eens kijken of dit eraf gehaald kan worden. Net zoals een
10
MAGAZINE
Na enig zoeken in de help en voorbeelden blijkt dat de grid standaard probeert om alle records naar binnen te slobberen, om zodoende de mogelijkheid tot sorteren, groeperen en filteren te bieden. Sorteren
DELPHI heeft namelijk geen zin als je maar een fractie van de onderliggende data in huis hebt. Net als filteren. Het klinkt erg aanlokkelijk om het cxGrid al deze zaken automatisch te laten verzorgen, maar als programmeur wil je toch proberen te voorkomen dat bij wijze van spreken de complete harde schijf van een server over het netwerk wordt getrokken.
onder de naam SdnGridUnit.pas. Hierna maken we (File-NewPackage – Delphi for Win32) een package dat we meteen opslaan met de naam SdnPack. Met de rechtermuis op SdnPack.bpl (in de project manager) kiezen we ‘add’ en voegen zo de unit toe met de class van hierboven. Daarna via rechtsklik kiezen voor ‘build’, gevolgd door ‘install’ en we zien:
Het geheim zit in de property GridMode van de DatamodeController, die zelf weer een property is van de DataController. Als je deze property wijzigt, ziet de code van je form er als volgt uit: object cxGrid1DBTableView1: TcxGridDBTableView NavigatorButtons.ConfirmDelete = False DataController.DataModeController.GridMode = True DataController.DataSource = DataSource1
Dus zo moeilijk is dat niet aan te passen. Tijd om het spul weer eens te runnen. En inderdaad staat het scherm er in tienden van seconden, zoals we van een grid (en Delphi) gewend zijn. En het klikken op een kolomkop kan nog steeds … alleen gebeurt er niks. Idem voor het filteren. En het groeperen op een kolom-kop heeft tot resultaat dat de hele tabel weer opgehaald wordt, en dus eindigt met een out-ofmemory boodschap. Dat laatste willen we zeker niet laten gebeuren, en het sorteren en filteren moet gewoon lekker op de database server gebeuren. Misschien biedt subclassen een oplossing.
Helaas, zo gemakkelijk komen we hier niet mee weg. Als we ons SDNgrid op een form zetten, staan alle properties nog keurig zoals bij het standaard cxGrid. En na wat aanmodderen bij de Create van de class TSdnGrid blijkt dat het hier niet mogelijk is om te gaan sleutelen in de eigenschappen van het Level of de View die bij de grid hoort. En wel om de heel eenvoudige reden dat deze beide steunpilaren bij de Create van de grid nog niet bestaan. De conclusie is dan ook dat we meteen het Level en de View ook tot een eigen subclass maken. Na wat aanmodderen kom ik erachter dat deze subclasses niet meteen als standaard Level en View door het SDN-grid worden geaccepteerd. Je moet je eigen Level en View aan de grid kenbaar maken door ze als default Level en View te benoemen. De hele class ziet er dan zo uit: TSdnLevel = class(TcxGridLevel)
Subclassing We beginnen met een gewone subclass van de grid:
TSdnRootLevel = class(TcxGridRootLevel) TSdnView = class(TcxGridDBTableView) TSdnTableViewInfo = class(TcxGridTableViewInfo)
TSdnGrid = class(TcxGrid)
TSdnRowsViewInfo = class(TcxGridRowsViewInfo)
private
TSdnGrid = class(TcxGrid)
{ Private declarations } protected { Protected declarations } public { Public declarations } constructor Create(AOwner: TComponent); override; destructor
Destroy; override;
Al met al is het een hele lap. En het enige dat ik hiermee heb bereikt is dat ik een ‘volledige’ grid heb (met View en Level) die klaar is om van onze eigen standaard kenmerken en gedrag te worden voorzien. Het wordt dan tijd om te zoeken naar de plek om de grid (of eigenlijk, de view) te dwingen standaard in GridMode=true te gaan. We maken daarom een override van de create van ons eigen TSdnView:
published { Published stuff } end;
constructor TSdnView.create(AOwner: TComponent); begin inherited create(AOwner);
procedure Register;
if not assigned(Datacontroller) then exit; if not assigned(Datacontroller.DataModeController)
implementation
then exit; Datacontroller.DataModeController.GridMode := true;
{ Register } procedure Register; begin RegisterComponents('SdnStuff',[TSdnGrid]); end; constructor TSdnGrid.create(AOwner: TComponent); begin inherited create(AOwner); end; destructor TSdnGrid.Destroy; begin inherited destroy; end; end.
end;
Let wel op dat het op deze manier een property is die altijd(!) wordt gezet, ook als je hem in design time zelf op false op zet. Maar in dit geval is het wel wat ik wil, dus kan ik ermee leven. De grid draait nu in ieder geval prima op een grote dataset. Het begin is er. Kolommen maken Nu maar verder gaan met het wensenlijstje waaraan mijn grid moet voldoen. Om te beginnen met het maken van de kolommen op het moment dat er runtime geen kolommen te bekennen zijn. En natuurlijk het opslaan (en weer ophalen) van de kolommen op het moment dat iemand de kolommen wijzigt. Op zoek dus naar zoiets als een ‘OnDatasourceActiveChanged’ event. Het blijkt niet eenvoudig. Het komt erop neer dat we nog meer van de TcxBlaBla classes moeten subclassen om zodoende een ActiveChanged event tot onze beschikking te hebben. Die vinden we terug in de DataProvider, en die is weer een onderdeel van de DbDataController. Dus we gooien nog twee subclasses in de strijd:
Volgens Bartjens zou alles prima kloppen, dus slaan we de unit op
magazine voor software development 11
DELPHI TSdnDataController = class(TcxGridDbDataController)
begin inherited ActiveChanged(AActive);
protected function GetDataProviderClass:
if assigned(OnActiveChanged) then OnActiveChanged(self);
TcxCustomDataProviderClass; override; end;
public { Public declarations } constructor Create(Aowner: TComponent); override; end;
Tenslotte maken we in onze TSdnView class een eigen procedure ActiveChanged, en in de Create van de TSdnView plakken we het zaakje aan elkaar:
TSdnDBDataProvider = class(TcxDbDataProvider) protected
constructor TSdnView.create(AOwner: TComponent);
procedure ActiveChanged(AActive: Boolean); override; end;
begin inherited create(AOwner); if not assigned(Datacontroller) then exit;
constructor TSdnDataController.create(Owner:TComponent);
if not assigned(Datacontroller.DataModeController)
begin
then exit;
inherited create(Owner);
Datacontroller.DataModeController.GridMode := true;
end; if assigned(DataController.Provider) then function TSdnDataController.GetDataProviderClass: TcxCustomDataProviderClass; begin
TSdnDbDataProvider(DataController.Provider). OnActiveChanged := ActiveChanged; end;
Result := TSdnDBDataProvider; end;
procedure TSdnView.ActiveChanged(sender: TObject); var
procedure
TSdnDBDataProvider.
ActiveChanged(AActive: Boolean); begin
sStorageName: string; begin // staat van data wijzigt: kolommen ophalen/opslaan
inherited ActiveChanged(AActive);
sStorageName := IncludeTrailingPathDelimiter(
end;
'Sdn\Grids\'+self.name); if DataController.Provider.DataSet.Active then
De initialisation wordt uitgebreid met:
begin if ColumnCount < 1 then DataController.CreateAllItems(false);
RegisterClass(TSdnDataController);
Self.RestoreFromRegistry(sStorageName, True, True,
Nou, we hebben nog vrijwel geen functionaliteit toegevoegd, maar wel 8 classes de wereld in geholpen. Mocht iemand opmerken dat het geheel wel redelijk ingewikkeld in elkaar zit, dan ben ik het daar grondig mee eens! Maar goed, we waren gebleven bij het maken van kolommen, en het opslaan en ophalen van instellingen. Nu we een eigen subclass hebben met die TSdnDbDataProvider kunnen we die een OnActiveChanged event geven waaraan we, vanuit onze eigen View-class, een procedure kunnen plakken. Dit doen we vanuit de View, aangezien op dit niveau alles bekend is qua kolommen, en omdat de View zelf wat procedures in zich heeft om de kolommen op te slaan dan wel op te halen. De TSdnDbDataProvider wordt uitgebreid met: TSdnDBDataProvider = class(TcxDbDataProvider) private
[gsoUseFilter,gsoUseSummary],''); end else begin if ColumnCount > 0 then self.StoreToRegistry(sStorageName,true, [gsoUseFilter,gsoUseSummary],''); end; end;
Zo. Het eerste stukje toegevoegde waarde zit erop. Het enige manco dat hier nog in zit is dat het vermoedelijk zo is dat, bij het sluiten van het form, de dataset of datasource eerder wordt afgekoppeld dan dat de dataset gesloten wordt. Dus om het netjes te doen moet ook nog zoiets in de Destroy van de TSdnView worden afgevangen. Dit beschouw ik maar even als behorend tot de afsluitende taakjes.
{ Private declarations } FOnActiveChanged: TNotifyEvent; protected { protected declarations } procedure ActiveChanged(AActive: Boolean); override;
Als we het TSdnGrid weer op een Form zetten met de eerder genoemde grote Interbase tabel, en het zaakje runnen, dan werkte het allemaal prima. Bij het openen en sluiten van de dataset (ibQuery) worden keurig de instellingen opgehaald en weggeschreven.
public { Public declarations } property OnActiveChanged: TNotifyEvent read FOnActiveChanged write FOnActiveChanged; end;
De ActiveChanged override die we hebben gemaakt wordt nu: procedure TSdnDBDataProvider. ActiveChanged(AActive: Boolean);
12
MAGAZINE
Als programmeur wil je toch proberen te voorkomen dat de complete harde schijf van een server over het netwerk wordt getrokken
DELPHI Sorteren van kolommen Dus kunnen we door met het volgende punt: het sorteren als op een kolomkop wordt geklikt. Het ligt voor de hand dat dit in de SQL gebeurt. De enige voorwaarde is dan dat er altijd een TQuery (of afgeleide) wordt gebruikt bij het opvragen van data. In mijn geval is dat ook zo, dus vooruit maar met de geit. Even wat spitten in de vele voorbeeldjes leert dat er een OnSortingChanged event bestaat binnen de Datacontroller van de View. Aangezien het meeste werk tot nu toe wel binnen de View wordt gedaan, maken we daarom maar een procedure SdnSortingChanged, die we in de Create aan het OnSortingChanged event van de Datacontroller plakken. Verder moet er nog een optie worden ‘aangezet’ : OptionsCustomize.ColumnSorting := True. Dit geeft de volgende toevoeging aan de code: procedure
TSdnView.SdnSortingChanged(Sender: TObject);
procedure ApplySortToQuery(AQuery: TIbQuery; ASortArray: array of string); var
ASortArray); finally EndUpdate; Screen.Cursor := crDefault; end; end;
Je ziet dat het eerste deel van de code uit de SortedItems list de SQL genereert die in de query kan worden gebruikt, en dat deze in ApplySortToQuery aan de SQL van de query wordt vastgeplakt. Let op dat er gemakshalve even niet wordt gelet op een sortering die in designtime aan de query wordt opgelegd, maar het is niet al te moeilijk om deze uitbreiding te verzinnen. Bij het testen (runnen) van deze uitbreiding blijkt dat er nog een cadeautje bij zit: op het moment dat de instellingen (in de SdnActiveChanged) worden opgehaald, wordt blijkbaar meteen de sortering gecheckt, en indien er kolommen worden opgeslagen waarop wordt gesorteerd, wordt deze sortering keurig netjes weer toegepast op het moment dat de grid weer actief wordt. Lekker makkelijk.
I: Integer; ASortString, ASqlString: string; begin ASortString := ''; for I := 0 to High(ASortArray) do ASortString := ASortString + ASortArray[I]; ASQLString := Uppercase(AQuery.Sql.text); Delete(ASortString, Length(ASortString)-1, 2); if pos('ORDER BY', ASQLString )>0 then ASQLString := copy(ASQLString,1,pos('ORDER BY',
Filteren Tot slot staat dan het filteren op mijn wensenlijstje. Een heel stuk terug hadden we al gezien dat bij een klik op de filter-button, de grid een lijstje produceert met de (unieke) waardes die in de onderliggende kolom kunnen voorkomen. Alleen in de GridMode die wij gebruiken werkt dit dus niet automatisch. Dat willen we ook niet, aangezien dit zou betekenen dat wederom de hele tabel in het geheugen moet worden gelezen. Zoals het nu is, gebeurt er helemaal niets, en wordt bij het klikken op de filter-button een bijna leeg lijstje getoond:
ASQLString )-1); try AQuery.DisableControls; AQuery.Close; if ASortString <> '' then ASortString := 'order by ' + ASortString; AQuery.Sql.Text := ASQLString + ASortString; finally AQuery.Open; AQuery.EnableControls; end; end; var
Het plan is om de tweede, derde, en vierde optie helemaal niet te tonen (ik wil mijn gebruikers niet gaan uitleggen wat blanks zijn). Verder moeten we dus m.b.v. SQL de unieke waardes gaan zoeken. Terug naar de help en de voorbeelden, en het blijkt gelukkig niet al te lastig. Binnen de View moet je de ‘OptionsCustomize.ColumnFiltering := True’ aanzetten. Dat kan dus prima in de Create erbij worden gezet. En er bestaat in de View.DataController.Filter property een ‘OnGetValueList’ event, dat afgevuurd wordt op het moment dat op de filter-button wordt geklikt. Dit event kan er dan als volgt uitzien: procedure TSdnView.SdnFilterGetValueList(
I: Integer;
Sender: TcxFilterCriteria; AItemIndex: Integer;
AOrder, AFieldName: string;
AValueList: TcxDataFilterValueList);
ASortArray: array of string; begin
function GetTableNameFromSql(AQuery: TIbQuery):
try
string;
Screen.Cursor := crHourGlass;
var I: Integer;
BeginUpdate;
ASqlString: string;
SetLength(ASortArray, SortedItemCount);
begin
for I := 0 to SortedItemCount - 1 do
result := '';
begin
ASqlString := uppercase(AQuery.Sql.Text);
AFieldName := TcxGridDBColumn( SortedItems[I]).DataBinding.FieldName;
if pos('FROM' ,ASqlString)>0 then begin
if SortedItems[I].SortOrder = soAscending then
Result :=trim(copy(ASqlString,
AOrder := ' ASC, '
pos('FROM',ASqlString)+4, length(ASqlString)));
else
i := 1;
AOrder := ' DESC, ';
while ((copy(Result,i,1)<>' ') ) do
ASortArray[SortedItems[I].SortIndex] := AFieldName + AOrder; end;
inc(i);
Result := trim(copy(Result,1,i-1)); end; end;
if Length(ASortArray) = 0 then SetLength(ASortArray, 1); ApplySortToQuery(
TIbQuery(DataController.DataSet),
var AColumn: TcxGridDBColumn;
magazine voor software development 13
DELPHI AValue: Variant;
TIbQuery(Datacontroller.DataSource.DataSet).close;
ASqlText: String;
s := uppercase(TIbQuery( Datacontroller.DataSource.DataSet).Sql.Text);
begin AColumn := Columns[AItemIndex];
if pos('WHERE',s) > 0 then s := Copy(s,1, pos('WHERE',s)-1);
try Screen.Cursor := crHourGlass;
if Datacontroller.Filter.FilterText<>'' then s := s + ' where '+Datacontroller.Filter.FilterText;
if not assigned(FQryTemp) then begin FQryTemp := TIbQuery.create(nil); FQryTemp.Database := TIbQuery( self.DataController.DataSource.DataSet).
TIBQuery(Datacontroller.DataSource.DataSet).Sql.Text :=s; TIbQuery(Datacontroller.DataSource.DataSet).Open;
Database; FQryTemp.Transaction
:= TIbQuery(
self.DataController.DataSource.DataSet). Transaction;
DataController.Filter.OnChanged := SdnFilterChanged; TSdnDbDataProvider(DataController.Provider). OnActiveChanged := SdnActiveChanged; end;
end; FQryTemp.SQL.Clear; ASqlText := 'Select DISTINCT ' + AColumn.DataBinding.FieldName + ' From ' + GetTableNameFromSql(TIbQuery(
Binnen de functie wordt het OnActiveChanged even afgekoppeld (en aan het eind weer aangekoppeld) om te voorkomen dat er onnodig instellingen worden opgeslagen.
DataController.DataSet)); FQryTemp.SQL.Add( ASqlText );
De Create van onze eigen View ziet er uiteindelijk zo uit:
FQryTemp.Open; FQryTemp.First;
constructor TSdnView.create(AOwner: TComponent);
while not FQryTemp.Eof do
begin
begin
inherited create(AOwner);
AValue := FQryTemp.Fields[0].Value;
if not assigned(Datacontroller) then exit;
if VarIsNull(AValue) then Exit;
if not assigned(Datacontroller.DataModeController)
AValueList.Add(fviValue, FQryTemp.Fields[0].Value, AValue, False);
then
exit;
Datacontroller.DataModeController.GridMode := true;
FQryTemp.Next; end;
// Voor het opslaan en ophalen van de instellingen
FQryTemp.Close;
if assigned(DataController.Provider) then
finally
TSdnDbDataProvider(DataController.Provider).
Screen.Cursor := crDefault;
OnActiveChanged := SdnActiveChanged;
end; end;
//voor middels SQL sorteren bij klikken op kolomkoppen DataController.OnSortingChanged := SdnSortingChanged;
Kort gezegd haalt dit stukje code alle waardes uit de tabel van het veld op wiens kolom wordt geklikt. Deze waardes worden dan in de ValueList gezet die wordt getoond op het moment dat op de filterbutton wordt geklikt. Even testen: en ja, dit werkt. Alleen gebeurt er niks met de onderliggende data op het moment dat we een waarde selecteren. Wat er wel gebeurt, is dat er het eerder genoemde paneel te voorschijn komt waarin de filter-expressie wordt getoond. En dat wilden we niet. Dit pakken we aan door in de Create van onze View het property Filterbox.visible op fvNever te zetten. Maar daarmee werkt het filter nog steeds niet. Na even zoeken blijkt dat de code binnen cxGrid gebruik probeert te maken van de normale ‘filter’-mogelijkheid die binnen een TDataset beschikbaar is. En dat willen we nou juist niet, aangezien dan alle records uit de onderliggende tabel weer naar binnen gehaald moeten worden. Dit gaan we voorkomen door (wederom in de Create van onze View) de property Datacontroller.Filter.AutoDatasetFilter op False te zetten. Daarnaast gebruiken we het Datacontroller.Filter.Onchanged event om het filter toe te passen op de onderliggende dataset: procedure TSdnView.SdnFilterChanged(Sender: TObject); var s: string; begin DataController.Filter.OnChanged := nil; TSdnDbDataProvider(DataController.Provider).
OptionsCustomize.ColumnSorting := True; // Voor het ophalen van de filter-waardes OptionsCustomize.ColumnFiltering := True; DataController.Filter.OnGetValueList := SdnFilterGetValueList; // filter-paneel niet zichtbaar Filterbox.visible := fvNever ; // geen dataset-filtering Datacontroller.Filter.AutoDatasetFilter := false; Datacontroller.Filter.OnChanged := SdnFilterChanged; end;
Als we dit testen blijkt dat we met het opslaan en ophalen van de instellingen nog een cadeautje krijgen: ook de filter-instellingen worden door de StoreToRegistry procedure keurig meegenomen, en andersom ook weer keurig toegepast. Lekker handig, en lekker consequent. Het enige nadeel dat ik hier zie is dat de filter-buttons alleen zichtbaar zijn op het moment dat de muis over de kolomkoppen heen gaat. En op het moment dat een filter (een extra stukje SQL) wordt opgeslagen, is het wel zo prettig voor de gebruiker dat het feit dat er een filter op een kolom actief is duidelijk zichtbaar is. Dus een laatste keer nog maar de helpfile in en de support afgestruind.
OnActiveChanged
Tijdens dit zoeken krijgen we meteen mee hoe we de filter-items die we niet willen hebben (de zgn. Blanks en Non-Blanks) eraf
:= Nil;
14
MAGAZINE
DELPHI moeten halen: for I := AValueList.Count-1 downto 0 do begin if AValueList.items[i].Kind in [fviCustom, fviBlanks, fviNonBlanks] then AValueList.Delete(i); end;
Dit stukje wordt dus meteen maar even meegenomen aan het begin van onze SdnFilterGetValueList. Opgeruimd staat netjes. Het andere bleek echter iets lastiger dan het zetten van een property. Het kwam erop neer dat we nog eens drie classes die in het cxGrid gebruikt worden, in een eigen klasse moesten vertalen, zodat we de GetAlwaysVisible van de ColumnHeaderFilterButtonViewInfo konden manipuleren. TSdnColumnHeaderFilterButtonViewInfo = class(TcxGridColumnHeaderFilterButtonViewInfo) protected function GetAlwaysVisible: Boolean; override; end; TSdnColumnHeaderViewInfo = class( TcxGridColumnHeaderViewInfo) protected procedure GetAreaViewInfoClasses(AProc: TcxGridClassEnumeratorProc); override; end;
Conclusie Het is zo een lang verhaal geworden. Maar het levert uiteindelijk een grid op die onder alle omstandigheden overeind blijft. Daaruit blijkt dat, wat je er ook aan wilt veranderen, alles uiteindelijk mogelijk is. Al die Win32-apps, waar de gebruikers uiteindelijk nog jaren mee te maken gaan krijgen, kunnen hiermee een zo groot mogelijk gebruiksgemak bieden. En snelheid. Gemakshalve heb ik het onderdeel ‘groeperen’ maar even buiten beschouwing gelaten. Net zoals het vertalen van teksten die je met gebruik van deze grid tegenkomt. Gelukkig bleek onderweg dat dit erg makkelijk te doen was. En neem maar van mij aan dat, als je toevallig een data-dictionary gebruikt, er een wereld van mogelijkheden voor je open gaat •
Marcel van Kalken Marcel van Kalken is een Delphiontwikkelaar van het eerste uur. Sinds het uitkomen van Delphi 1 in 1995 houdt hij zich voor een bont gezelschap van opdrachtgevers bezig met het ontwikkelen en onderhouden van Delphi/Win32 applicaties. Voor 1995 was hij al een kleine 10 jaar met pc’s actief, met als belangrijkste gereedschappen Clipper, VB, en Visual Objects. Behalve Delphiadept is hij ook getrouwd, vader van 2 dochters, en liefhebber van sport (hockey en windsurfen) en fotografie.
TSdnHeaderViewInfo = class(TcxGridHeaderViewInfo) function GetItemClass: TcxGridColumnHeaderViewInfoClass; override; end; function TSdnColumnHeaderFilterButtonViewInfo.
Visual Basic
TIP:
GetAlwaysVisible: Boolean;
Lambda functies kunnen delegates vervangen
begin Result := inherited GetAlwaysVisible or (GridView.DataController.Filter.FindItemByItemLink( Column) <> nil);
Op punten waar vroeger een delegate gebruikt moest worden kan in Visual Basic 9 vaak ook een lambda expressie gebruikt worden. Bijvoorbeeld:
end; lblX.Invoke(Function() SetProgressLabel2(percentage)) procedure TSdnColumnHeaderViewInfo. GetAreaViewInfoClasses( AProc: TcxGridClassEnumeratorProc);
Zo veel makkelijker dan het gebruik van een delegate en we hebben meteen controle op het gebruik door de compiler…
begin if CanHorzSize then AProc( TcxGridColumnHeaderHorzSizingEdgeViewInfo); if CanFilter then AProc( TSdnColumnHeaderFilterButtonViewInfo); if CanSort then AProc( TcxGridColumnHeaderSortingMarkViewInfo); if HasGlyph then AProc(
Visual Objects
TIP:
TcxGridColumnHeaderGlyphViewInfo); end; function TSdnHeaderViewInfo.GetItemClass: TcxGridColumnHeaderViewInfoClass; begin Result := TSdnColumnHeaderViewInfo; end;
SetFilter() en query optimization De DBFCDX RDD beschikt over een query optimizer voor SetFilter. Belangrijk is te weten dat dit alleen werkt als de filterexpressie (ook) als string wordt doorgegeven. Als de expressie veldnamen bevat, zal de optimizer gebruik maken van indexen welke dezelfde veldnaam bevatten. De maximale lengte van de filter-expressie string is voor VO 254 tekens. Voor Vulcan is dit verruimd.
magazine voor software development 15
Advertentie Sira Holding b.v.
.NET
VISUAL BASIC
Stephan Smetsers
Softwaredocumentatie met
Sandcastle Waarom verschijnt er altijd een ongemakkelijke glimlach op het gezicht van een ontwikkelaar als er gevraagd wordt of zijn software gedocumenteerd is? Hoewel deze vraag niet wordt beantwoord in dit artikel, zal er worden uitgelegd hoe het op eenvoudige wijze mogelijk is om (technische) documentatie van software automatisch te laten genereren. Fig. 1: IntelliSense C# IntelliSense De ontwikkelomgeving Visual Studio van Microsoft biedt een ontwikkelaar veel hulpmiddelen die gebruikt kunnen worden om gemakkelijk software te kunnen schrijven. Een van deze hulpmiddelen is de zogenaamde IntelliSense. Reeds tijdens het intypen van de code analyseert de compiler van Visual Studio de software en geeft op basis daarvan hints en tips aan de ontwikkelaar. Deze hints en tips beperken zich in eerste instantie tot het geven van technische informatie
XML-comments als input voor generatie van technische documentatie Fig. 2: IntelliSense Visual Basic over de werking van bepaalde statements en hoe programmafuncties aangeroepen dienen te worden. Het geven van begrijpbare functionele uitleg ontbreekt hier natuurlijk. Gelukkig is het wel mogelijk om handmatig deze uitleg toe te voegen aan de IntelliSense van Visual Studio door middel van het definieren van XML-comments. Deze worden vaak als triple-slash-comments genoemd. De naamgeving triple-slash-comments is ontstaan vanwege het feit, dat een dubbele slash gebruikt wordt om commentaar in een C, C++ of C# listing op te nemen. Specifiek commentaar voor IntelliSense wordt gedefinieerd door een drievoudige slash. Bij het gebruik van Visual Basic als programmeertaal worden geen slashes gebruikt maar het apostrofteken. Daar waar in C# een drievoudige slash wordt gebruikt, wordt in Visual Basic een drievoudige apostrof gebruikt. In de volgende listings is een voorbeeld weergegeven voor zowel C# als Visual Basic, dat laat zien hoe de IntelliSense van Visual Studio het commentaar van de functie GetFilename weergeeft.
Veel ontwikkelde software wordt als een library gedistribueerd (classlibrary). Andere softwareontwikkelaars kunnen naar deze libraries referenties leggen om daaruit onderdelen te gebruiken. Natuurlijk is het wenselijk dat de IntelliSense, die is uitgebreid met de triple-slashcomments, ook voor deze ontwikkelaars beschikbaar komt. Omdat triple-slash-comments geen onderdeel zijn van de gecompileerde programmacode, zou je verwachten dat ook de listing meegeleverd moet worden. Gelukkig is dit niet het geval en biedt Visual Studio de mogelijkheid om uitsluitend alle triple-slash-comments in een speciaal bestand op te slaan. De gedistribueerde software bestaat dus uit twee onderdelen: de gecompileerde programmacode (DLLbestand) en de triple-slash-comments ten behoeve van de IntelliSense (XML-bestand). In de volgende afbeelding is zichtbaar waar in het properties-scherm geconfigureerd wordt dat Visual Studio een dergelijk XML-bestand dient te genereren (deze optie staat standaard uit).
magazine voor software development 17
.NET VISUAL BASIC Sandcastle is niet het eerste product dat op basis van XML-comments in staat is om documentatie te genereren van de programmacode. De meeste ontwikkelaars hebben vast wel eens gehoord van NDoc. Dit opensource project was een van de eerste die volledig geautomatiseerd een helpbestand kon genereren in diverse bestandsformaten. Helaas is dit project stopgezet door de auteur van NDoc omdat hij niet of nauwelijks hulp kreeg vanuit de community om het product verder te ontwikkelen en geschikt te maken voor de nieuwere versies van .NET. Het antwoord van Microsoft kon daarom niet uitblijven en zo kwam het project van de grond, dat nu de codenaam Sandcastle draagt.
Fig. 3: Propertiesscherm Sandcastle Naast het IntelliSense mechanisme dat volledig is geintegreerd in de Visual Studio ontwikkelomgeving, zijn er nog andere hulpmiddelen die gebruik maken van de triple-slash-comments. Een van deze hulpmiddelen is een door Microsoft ontwikkeld product dat voorlopig nog de codenaam Sandcastle draagt. Sandcastle is een product dat op basis van de door Visual Studio geleverde XML-bestanden technische documentatie kan genereren van de ontwikkelde programmacode. Deze gegenereerde documentatie bevat naast alle teksten die via triple-slash-comments door een ontwikkelaar zijn ingevoerd, ook alle teksten die door ontwikkelaars van Microsoft zijn ingevoerd. Dit laatste geldt natuurlijk alleen voor gemeenschappelijke functies die zowel in de eigen ontwikkelde software als ook in het .NET Framework aanwezig zijn. Daarnaast bevat de gegenereerde documentatie ook verwijzingen naar de MSDN-website van Microsoft waar de volledige documentatie van het .NET Framework te vinden is. In de volgende afbeelding is zichtbaar hoe de gegenereerde software documentatie door Sandcastle er uitziet. De look-and-feel van de documentatie vertoont veel gelijkenis met die van de MSDN-website van Microsoft. Op zich is dit niet raar aangezien insiders bij Microsoft bevestigd hebben dat ook Microsoft gebruik maakt van Sandcastle.
Werking van Sandcastle De werking van Sandcastle bestaat uit een aantal aaneen geschakelde verwerkingsstappen. Als eerste wordt via Visual Studio de programmacode gecompileerd. Dit resulteert in twee soorten bestanden namelijk de gecompileerde programmacode (DLL-bestand) en de XML-comments (XML-bestand). Vervolgens zal het DLL-bestand door Sandcastle worden geanalyseerd. Dit is te vergelijken met de analyse die de compiler van Visual Studio uitvoert ten behoeve van de IntelliSense. De uitvoer van deze analyse levert een aantal XML-bestanden op. Deze bestanden worden via een aantal XSLT-transformaties samengevoegd met de door Visual Studio gegenereerde XML-comments-bestanden en omgevormd tot leesbare HTML-bestanden. Deze HTML-bestanden worden uiteindelijk via een HTML-helpbuilder gecompileerd tot een HLP- en/of CHM-bestand. In de volgende afbeelding zijn de verwerkingsstappen van Sandcastle weergegeven.
Fig. 5: Sandcastle documentatieproces Sandcastle beschikt niet over een (gebruiksvriendelijke) user interface. De reden hiervoor is voor buitenstaanders nog een beetje speculeren, maar dit heeft hoogstwaarschijnlijk te maken met het feit Microsoft het gebruik van Sandcastle ziet als onderdeel van een geautomatiseerd build-proces (wat vaak tijdens een nachtelijke run plaatsvindt). Gelukkig zijn er door een aantal leden van de Microsoft community diverse tools gemaakt die voorzien zijn van een user interface en die Sandcastle onder water configureren en aansturen. Deze maken het gebruik van Sandcastle, dat vooralsnog beschikt over een commandline interface, eenvoudiger. Deze tools worden door Microsoft ook aanbevolen om te gebruiken.
Fig. 4: Gegenereerde documentatie door Sandcastle
18
MAGAZINE
Conclusie Steeds meer bedrijven erkennen dat het beschikken over goede documentatie een essentieel onderdeel is van het proces dat softwareontwikkeling heet. Omdat er steeds hogere eisen gesteld worden aan de kwaliteit van dergelijke documenten, neemt ook de tijd die nodig is om documentatie schrijven toe. Het gebruik van adequate tooling om dit proces te versnellen is dan ook een onmisbaar onderdeel. Met behulp van Microsoft’s Sandcastle kan op eenvoudige en snelle wijze een overzichtelijke technische documentatie worden gegenereerd van de ontwikkelde software. Het is vanzelfsprekend dat het uitsluitend beschikken over technische documentatie niet
.NET VISUAL BASIC voldoende is en er moet onderkend worden dat de aanwezigheid van functionele documentatie minstens zo belangrijk is. Microsoft’s Sandcastle mag gezien worden als een goede aanvulling op de hulpmiddelen die bijdragen aan het schrijven van documentatie van software. Meer informatie over Sandcastle, de syntaxis van XML-comments en de beschikbare user interfaces voor Sandcastle kan gevonden worden op de volgende websites: • http://msdn.microsoft.com/en-us/library/b2s063f7.aspx • http://www.sandcastledocs.com • http://www.inchl.nl/sandcastlegui •
Stephan Smetsers Stephan heeft een eigen bedrijf gehad dat technische software ontwikkelde voor de architectenbranche ten behoeve van geautomatiseerde NEN-normberekeningen voor de optimalisatie en ondersteuning van het bouwvoorbereidingsproces. Sinds februari 2005 is Stephan parttime in dienst bij Atos Origin als senior .NET ontwikkelaar. Naast deze werkzaamheden ontwikkelt hij zelfstandig software, met name generieke frameworks en componenten, die veelal bij eigen projecten worden ingezet.
Advertentie Aladdin
.NET
TIP:
Verbergt IntelliSense je code? Verbergt de IntelliSense popup een deel van de code die je wilt kunnen zien?. Druk dan op de Control-toets! Door op de Control-toets te drukken wordt het IntelliSense-venster transparant en is de code die erachter staat te lezen.
.NET
TIP:
Project Mole Met Visual Studio Debug Visualizers kan het leven van een ontwikkelaar een stuk makkelijker worden. Maar om ze zelf te maken kost weer tijd en dat is iets waar we toch al te weinig van hebben. Gelukkig is Karl Shifflett met een paar vrienden druk bezig geweest en hebben zij Mole ontwikkeld, de Debug Visualizer voor bijna alles. Zie http://www.codeproject.com/KB/ macros/MoleForVisualStudioEdit.aspx voor meer info.
Advertentie Logica
DOT NET NUKE
Lee Sykes
DotNetNuke Maintenance Power Tips As DotNetNuke becomes a more established CMS, DotNetNuke web designers will find that clients are requesting more often to re-skin or carry out maintenance tasks on already established DotNetNuke websites. These maintenance tasks and re-skinning jobs can be very time consuming. I would like to walk you through a typical scenario that I come across with my clients and provide you with some power tips for literally reducing weeks and months worth of work into seconds. These examples are not just limited to DotNetNuke, these examples can apply to any CMS and you may find the techniques outlined here useful for managing other websites. The Client Let’s take a typical example: two years ago you created a DotNetNuke website for a client, the site used a table based skin and once you created the site the client then maintained the site adding content. The site now consists of 500 pages. The client now wants to update the skin design for a new look, and they also wish to take advantage of a pure CSS skin for benefits such as reduced page size, search engine optimized pages, and accessibility. Let’s go through some of the typical problems and lengthy administration tasks we would typically have to carry out.
Reduce weeks of administration work into seconds! Typical problems Junk HTML When adding content into the Text/HTML modules, the client has formatted all of the content. So, the source code is full of junk HTML, ie. Example Text
The problem is that if you delete the Links module from one of the pages, it will only be deleted from that single page and will remain on the other 499 pages. Task: We need to delete a module from 500 pages. Typical time length: 3 minutes per page = 1500 minutes. Shadow/Copied Modules There are several instances of modules copied to various pages within the website. The containers for these individual copied modules will all need updating. Task: We need to find out which pages the copied modules are located on and change the container for the copied modules on those pages. How do you find the pages with the copied modules? Typical time length: 3 minutes per page = 1500 minutes. Change the skin on individual pages Typically on a DotNetNuke installation you would set the skin for all pages in the admin menu. For some pages however you will specify a different skin to use for an individual page. Task: We need to identify which pages have a skin specified, go to the page settings for each of those pages and select the new CSS skin to use for the page. How do you find the pages that use a different skin? Typical Time Length: 3 minutes per page = 1500 minutes.
Task: We need to strip out all elements of junk HTML code in order for the new CSS skin to display correctly. Typical time length: 20 minutes per page to manually strip out the code = 10,000 minutes.
Moving modules to new content panes The old skin has a content pane named ‘Top Right’; the client wants to remove this pane and move those modules to the ‘Right’ pane. Task: Visit each page, using the module settings move each page to the right pane. Typical Time Length: 5 minutes per page = 2500 minutes.
Modules Displayed on all Pages Currently a Links module is set to display on all pages. The Links module contains links to key areas of the website. Instead of using this Links module we can embed a menu skin object within our new CSS skin which will enable those links to be more easily maintained.
17000 minutes of administration time You should now get an idea of the typical tasks we are faced with and some of the major problems of time. So far, without creating the skin, just administration time of manually logging into DotNetNuke and making changes to the pages, modules, skins and containers, time is
Listing 1: Typical junk HTML code
magazine voor software development 21
DOT NET NUKE going to be around 17,000 minutes or 283 hours or 7 weeks of work. This is clearly not an option. Time to panic? The first time I came across this problem, I spent a few days manually administrating the DotNetNuke portal when I suddenly realized that to complete the volume of work for this particular client it was going to take around three months of administration time. With a month deadline for the completion of the site I began to think that there must be ways of automating these repetitive tasks and speeding up the administration process. First Steps My first source of inspiration came from Kevin Schreiner of R2i where Kevin published a tip for fixing broken links when moving a site from a localhost installation to a live site and vice versa. The tip outlines how to use a Find and Replace SQL procedure to search for links within the Text/HTML modules and fix them to the correct link, further information can be read here: http://www.bi4ce.com/tabid/106/forumid/2/postid/86/view/topic/Default.aspx It occurred to me that I could take advantage of this Find and Replace procedure to find junk HTML code in the Text/HTML modules and strip it out. All I needed to do was to identify the junk HTML. Identify a Text/HTML module We need to identify the ID of a Text/HTML module and view the data that is stored for this module directly within the DotNetNuke database tables. As an example, visit a page which contains junk HTML in the Text/HTML module. To identify the ID of the Text/HTML module on the page, go to the modules settings and view the URL for the settings page, a typical example is: http://www.awebsite.com/tabid/403/ctl/Edit/mid/346/Default.aspx Here we can identify the ModuleID = 346 and the TabID = 403 (ID of the page) (NOTE: In DotNetNuke the terms ‘Page’ and ‘Tab’ are interchangeable.)
SELECT * FROM HtmlText WHERE ModuleID = 346
Listing 2: Viewing the data of the Text / HTML module In the results, the DesktopHtml column holds the text data for the module. We now need to view this data, identify the junk HTML and apply it to the Find and Replace query.
Fig. 2: Viewing the data for the Text / HTML module with the ID 346 Find and Replace Over the period of 2 years, the client has used several text editor versions, such as the Free Text Box and FCK Editor. Because of this I discovered that the text editors had stored the junk HTML in different ways, so I had to analyse a lot of different modules content to create a definitive list of junk HTML code. For this client I found 80 different variations of HTML code that I needed to Find and Replace. In listing 3 are some examples of the HTML code I removed using Kevin Schreiner’s Find and Replace procedure: EXEC Replace_DesktopHtml '','' EXEC Replace_DesktopHtml '','' EXEC Replace_DesktopHtml '
','
' EXEC Replace_DesktopHtml '<span style="FONT-FAMILY: verdana;">',''
Listing 3: Example Find and Replace HTML to remove If we analyze the first line from listing 3:
This represents:
Fig. 1: Module Settings Menu View a Text/HTML module in the database View your DotNetNuke database using Microsoft SQL Server, locate and view the content from the HtmlText table. Within this table you can view the data stored for every single Text/HTML module in your DotNetNuke installation. If we need to find the data for the Text/HTML module with the ModuleID of 346, we can do this with a simple query:
22
MAGAZINE
When we execute the Find and Replace procedure, the content inside the first set of apostrophes is the HTML content to find and the content inside the second set of apostrophes is the content to replace it with. Therefore, in the first line from listing 3 we are finding the HTML content, but there isn’t any content inside the second set of apostrophes, so that HTML content will be deleted. If we look at line 3 from listing 3: '
','
'
This represents: Find:
and Replace with
DOT NET NUKE Execute the Find and Replace query Once I had identified the HTML code to Find and Replace I was able to quickly remove it by executing the Find and Replace procedure. What was a 10000 minute job was now reduced into a few seconds. Of course, checks need to be made to the pages to ensure the correct HTML has been removed and has not caused display problems.I backed up my database at every stage when executing these queries and tested extensively. Step Two Having executed these SQL queries to speed up administration time removing junk HTML code, it occurred to me that I could use SQL to automate all of the remaining repetitive tasks. Let’s go through some quick examples. Pages, Modules, Skins and Containers If you look through the DotNetNuke installation tables, the two important tables are: • Tabs: This holds the data for each page’s settings, ie. Skin and container settings, title, description and keywords. • TabModules: This holds the data for which module is displayed on each Tab (page). Delete a module from all pages So, the next problem was to delete the Links module which was set to display on all pages. This is now simple using SQL. First of all identify the ID of the Links module: go to the Links module settings page (view figure 1 for an example), eg: - http://www.awebsite.com /tabid/36/ctl/Module/ModuleId/351/ Default.aspx We can now construct a SELECT query to check we are going to delete the correct data:
SQL is a Lifesaver! From those examples you can see how SQL can reduce DotNetNuke administration time from weeks of work to seconds. All you need is a small amount of SQL knowledge and basic queries can solve many problems. The technique I tend to use is to create a test page with modules and then analyze the data for the test page in the DotNetNuke database. From there I can create test SQL queries to manipulate the test page and once I am satisfied with how it works I then apply the SQL queries to the entire DotNetNuke installation. From a little inspiration I was able to think outside the box and move away from manually administrating my DotNetNuke portals. So, next time you find yourself administrating a repetitive task, think to yourself, could I speed this up with a SQL query? 99% of the time I think you will be pleasantly surprised •
Lee Sykes Lee Sykes is the publisher of DNN Creative Magazine and is based in the UK. The magazine is a resource to provide video training on DotNetNuke from beginner getting started guides to advanced skinning techniques. Lee has been working with DotNetNuke since version 2 and is dedicated to helping the community gain the most from their DotNetNuke websites. For more information and many free articles and resources please visit: www.dnncreative.com
SELECT * FROM TabModules WHERE ModuleID = 351
Listing 4: Selecting all copied instances of a Links module
.NET
TIP:
Conversie C# <-> VB.NET
Fig. 3: Viewing all instances of the Links module, in the TabID column you can see these modules are displayed on separate pages This will display in the results every single instance of the Links module, we now know we are targeting the correct data, so we can change this to a DELETE query.
Ben je een VB developer en heb je moeite met een stuk code wat je hebt gekregen in C#? Lastige haken en puntkomma’s? Of ben je een C# programmeur en vertik je het om te programmeren in VB maar heb je van je baas toch de opdracht gekregen om een stukje in VB aan te leveren? Dan is hier voor jou de oplossing, het licht in de duisternis, het water in de woestijn: http://labs.developerfusion.co.uk/convert/vb-to-csharp.aspx http://labs.developerfusion.co.uk/convert/csharp-to-vb.aspx Met deze site kun je stukken code uit de ene taal omzetten naar de andere. Even compileren om te kijken of alles goed is gegaan, maar het scheelt je in ieder geval een flink stuk uitzoekwerk als je niet bekend bent met de andere omgeving!
DELETE TabModules WHERE ModuleID = 351
Listing 5: Deleting all copied instances of a Links module This would have taken around 1500 minutes to delete this module manually from every single page, we have now deleted the module in seconds.
magazine voor software development 23
.NET
VISUAL C#
Mendelt Siebenga
IEnumerable, de basis voor LINQ LINQ is een van de meest interessante nieuwe ontwikkelingen op het .Net platform. Er is al veel geschreven over lambda expressies en extension methods, de twee technieken die aan C# 3.0 zijn toegevoegd om LINQ mogelijk te maken. In dit artikel gaan we in op het IEnumerable interface. Dit interface is al onderdeel van het .Net platform sinds de eerste versie en maakt een aantal interessante programmeertechnieken mogelijk (waaronder LINQ). Arrays, IEnumerable en Linq Een stuk code zegt meer dan duizend woorden. Laten we eens kijken, waar we het in dit artikel over gaan hebben: zie listing 1. int[ ] eenTotTien = new int[ ] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; foreach( var teller in eenTotTien ) Console.WriteLine( teller ); var even =
Een dergelijk array lijkt simpel maar schijn bedriegt Een array is een stuk krachtiger dan je zou verwachten van een primitief datatype. Er worden zeven interfaces ondersteund en daar hebben we geen letter voor hoeven te programmeren. Op de achtergrond doet het .Net framework een heleboel voor ons. De interessante interfaces (in de context van dit artikel) zijn IEnumerable en zijn generic broertje IEnumerable. In listing 3 staat het IEnumerator interface uitgeschreven.
from n in eenTotTien where n % 2 == 0 select n;
public interface IEnumerable { IEnumerator GetEnumerator( );
foreach( var evenTeller in even )
}
Console.WriteLine( evenTeller ); public interface IEnumerator : IEnumerator
Listing 1
{
Een dergelijk array lijkt simpel maar schijn bedriegt. In het voorgaande stuk code doen we al twee dingen die je niet met ieder object kunt doen. De compiler weet blijkbaar hoe je met een foreach-lus door de waarden in een array kunt itereren en zelfs hoe je met LINQ een selectie uit het array kunt maken. Het is interessant om te zien wat er hier onder de motorkap precies gebeurt. De eerste hint krijgen we als we kijken naar de interfaces die door de array worden geimplementeerd. In listing 2 staat het in stijl van een LINQ query.
}
T Current { get; }
var interfaces =
public interface IEnumerator { object Current { get; } bool MoveNext( ); void Reset( ); }
Listing 3
from n in typeof( int[ ] ).GetInterfaces( ) select n.ToString( ); foreach( var name in interfaces ) Console.WriteLine( name );
Listing 2
24
MAGAZINE
Het iterator pattern Een array en andere IEnumerable objecten, zoals de List, hebben een GetEnumerator functie. Met deze GetEnumerator functie kan de enumerator van het object opgevraagd worden. Deze enumerators bevatten functionaliteit om één voor één door de elementen heen te lopen en eventueel met Reset weer opnieuw te beginnen. Hierin valt
.NET VISUAL C# duidelijk het iterator pattern te herkennen. Het is dus ook mogelijk om meerdere IEnumerators tegelijk op dezelfde collectie te laten werken zoals te zien in listing 4.
public bool MoveNext( )
int[ ] eenTotTien = new int[ ]
}
{ _current++; return _current <= 10;
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; public void Reset( ) {
foreach( var teller1 in eenTotTien )
_current = 0;
foreach( var teller2 in eenTotTien ) }
Console.WriteLine( }
string.Format( "{0} - {1}", teller1, teller2 ) ); }
Listing 4 Listing 6 Zoals uit listing 5 blijkt, vertaalt de C# compiler onder water de twee foreach-lussen. int[ ] eenTotTien =
Listing 7 toont hoe deze enumerator aangeroepen kan worden. foreach( var teller in new EenTotTien() )
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; IEnumerator enumerator1 =
Console.WriteLine( teller );
Listing 7
( eenTotTien as IEnumerable ).GetEnumerator( ); IEnumerator enumerator2 = ( eenTotTien as IEnumerable ).GetEnumerator( ); enumerator1.Reset( ); while( enumerator1.MoveNext( ) ) { enumerator2.Reset( );
De EenTotTien klasse implementeert het IEnumerator interface en kan in een foreach-lus gebruikt worden. Alle functionaliteit om dit voor elkaar te krijgen zit in TienEnumerator. Ik heb hier wel een beetje vals gespeeld. De EenTotTien klasse bevat zelf geen collection maar laat alles over aan de TienEnumerator, en deze berekent de getallen op het moment dat ze nodig zijn.
while( enumerator2.MoveNext( ) ) { Console.WriteLine( string.Format( "{0} - {1}", enumerator1.Current, enumerator2.Current ) ); } }
Listing 5 De twee enumerators op hetzelfde array hebben geen invloed op elkaars positie.
Functioneel programmeren met Yield Return Tot nu toe hebben we mooie dingen gezien en worden de voorbeelden steeds langer. Er is een maar: we hebben nu dus twee klassen nodig om simpelweg tot tien te kunnen tellen. Gelukkig is er sinds versie 2.0 van het .Net framework een eenvoudiger manier bijgekomen. C# 2.0 implementeert met het yield return keyword zogenaamde continuations. Een functie met een IEnumerable als return type kan in stukjes worden uitgevoerd. Iedere keer dat een waarde uit de IEnumerable wordt teruggegeven of wordt opgevraagd, wordt precies genoeg van de functie uitgevoerd om de volgende waarde te berekenen. Met listing 8 wordt dit duidelijker. static IEnumerable TelTotTien( ) {
Doe het zelf We kunnen nu in onze eigen klasses IEnumerable implementeren om sequentiele data beschikbaar te maken. In listing 6 staat dit uitgewerkt.
for( int nummer = 1; nummer <= 10; nummer++ ) { yield return nummer; }
class EenTotTien : IEnumerable
}
{ public IEnumerator GetEnumerator( )
Listing 8
{ return new TienEnumerator( ); }
Dit kan op ongeveer dezelfde manier worden aangeroepen als het vorige voorbeeld:
public class TienEnumerator : IEnumerator
var totTien = TelTotTien( );
{
foreach( int teller in totTien ) private int _current = 0;
{
public object Current
}
Console.WriteLine( teller ); {
Listing 9
get { return _current; } }
Mocht je nog niet bekend zijn met deze constructie, dan is het misschien een goed idee de voorbeeldcode een keer in de debugger uit te voeren en breakpoints te zetten. Hiermee krijg je een duidelijk beeld van de volgorde waarin de code wordt uitgevoerd. Dit heeft mij
magazine voor software development 25
.NET VISUAL C# geholpen om de door te krijgen wat er gebeurt. Dit begint een beetje te lijken op wat functionele programmeertalen doen. De IEnumerable kan worden doorgegeven, zonder dat de achterliggende code wordt uitgevoerd. Pas op het moment dat er resultaten worden opgevraagd, wordt deze uitgevoerd. Op deze manier is het mogelijk foreach-lussen aan elkaar te hangen en bewerkingen op sequentiele gegevens toe te passen. We kunnen de twee volgende functies aan het vorige voorbeeld toevoegen: zie listing 10 als resultaat.
Conclusie In dit artikel hebben we met een paar simpele voorbeelden het IEnumerable interface laten zien. Dit interface is voor meer dan alleen arrays of collections interessant. Door het gebruik van dit interface is het mogelijk op een elegante manier flexibele en efficiente code te schrijven. In eerdere versies van het .NET framework was dit nog veel werk, maar door gebruik van yield return en met de komst van LINQ is het implementeren en het gebruiken van het IEnumerable interface een stuk makkelijker en leesbaarder geworden.
static IEnumerable AlleenEven( IEnumerable input ) { foreach( var nummer in input ) { if( nummer % 2 == 0 ) { yield return nummer; } } }
We noemden in dit artikel eerder de overeenkomsten met functionele programmeertalen. De grens tussen het resultaat van een bewerking en de bewerking zelf vervaagt. Hierdoor heeft een compiler meer vrijheid om te bepalen wanneer bewerkingen uitgevoerd worden. Ook wordt het makkelijker om bewerkingen in parallel uit te voeren. Met de CTP voor de Parallel Extensions to .Net is dat precies wat Microsoft nu doet. Een onderdeel daarvan is PLINQ, waarmee Linq queries parallel uit te voeren zijn. Voor meer informatie op kijk je op: http://www.microsoft.com/downloads/details.aspx?FamilyID=e848dc 1d-5be3-4941-8705-024bc7f180ba&displaylang=en •
static IEnumerable KeerTwee( IEnumerable input )
Mendelt Siebenga
foreach( var nummer in input )
Mendelt Siebenga is werkzaam als senior software engineer bij Enigmatry en is gespecialiseerd in C#, Agile en TDD. In zijn vrije tijd verruilt hij graag zijn toetsenbord voor een soldeerbout. (http://blogs.enigmatry.com/ Mendelevium)
{ { yield return nummer * 2; } }
Listing 10 Deze kunnen we achter elkaar uitvoeren en zo door het resultaat heen itereren (zie listing 11). var totTien = KeerTwee( AlleenEven( TelTotTien( ) ) ); foreach( int teller in totTien ) { Console.WriteLine( teller ); }
Listing 11 Dit geeft ons de mogelijkheid om heel flexibel filters en operaties toe te passen op rijen gegevens. En deze worden pas uitgevoerd als de resultaten ook daadwerkelijk nodig zijn. En dan nu ... LINQ Wat het vorige voorbeeld laat zien, lijkt eigenlijk heel erg op wat LINQ doet. In de System.Linq namespace is een klasse Enumerable aanwezig met een hele verzameling extension methods op IEnumerable. Deze kunnen precies zo gebruikt worden als in het vorige voorbeeld. Het enige dat de C# compiler nog hoeft te doen, is de LINQ syntax vertalen naar deze functies. Als je het allereerste voorbeeld in bijv. de reflector stopt, dan zie je het volgende resultaat: IEnumerable even = eenTotTien.Where( anonymousDelegate );
Listing 12 De where clause is vertaald naar een anonymous delegate die wordt meegegeven aan een where clause. De Where is een weer extension method op IEnumerable, die een IEnumerable teruggeeft.
26
MAGAZINE
Meer info over SDN Conference: conference.sdn.nl
Advertentie Giraffe
DATABASES
Cary Jensen & Loy Anderson
First Look at Advantage Database Server 9 What Is the Advantage Database Server? The Advantage Database Server is a high-performance, relational database server that simultaneously supports optimized set-based SQL, as well as a blinding-fast, index-based navigational model. Its low per-seat cost, ease of deployment, and very low maintenance requirements makes it particularly well suited for vertical market applications, especially those where the deployed sites lack the IT infrastructure required to maintain normal database servers. At its core, ADS is an ISAM (indexed sequential access method) database. However, unlike classic ISAM databases, such as Clipper, dBase, and FoxPro, ADS is a true, transaction-supporting, remote database server. And with its extensive optimized support for SQL, it provides a best-of-all-worlds solution to data access. Originally designed to provide Clipper developers with a remote server for Clipper files, it has grown into a first-class server on its own right, providing its own, high-performance file system, in addition to supporting traditional Clipper and FoxPro tables. (Version 9 now supports Visual FoxPro tables as well. This is discussed in more detail later.) We simply cannot stress the value of the simultaneous support for both the set-based SQL and navigational models with Advantage. On the largest project I am currently working on (an ASP.NET project), our team regularly creates SQL statements that join 30 or more tables, including a handful of tables that contain tens of millions of records. These queries execute in a fraction of a second. At the same time, we also get to use the Advantage Data Provider for
The Advantage Database Server is a high-performance, relational database server that simultaneously supports optimized set-based SQL, as well as a blinding-fast, index-based navigational model 28
MAGAZINE
With the release of Advantage Database Server 9, Sybase has confirmed its commitment to this powerful yet easy-to-use database server. This article is designed to provide you with a first look at the new and improved features in this latest release of this remarkable database product. This article begins with a quick overview of the Advantage Database Server (ADS), including the features that make it stand out in the world of database servers. It continues with a look at many of the enhancements introduced in this latest release. This discussion is intended to highlight the bigger improvements, as well as those that are of particular importance to large groups of ADS users. In other words, there are more updates in Advantage Database Server 9 than can be covered in this article.
.NET, which is the only .NET data provider we are aware of that provides server side cursors. Specifically, the Advantage Data Provider for .NET provides a DataReader descendant, called the AdsExtendedReader that supports bi-directional navigation, index-based seeks, pessimistic locking, and read/write capability. And its performance is nothing short of spectacular. Advantage is also available in a local file server version, similar to the traditional dBase tables of MS Access databases and the Borland Database Engine (BDE). This version, the Advantage Local Server, is free, providing developers with a no-cost solution for delivering database that do not need the reliability of a remote database server, all the while providing their clients with a low-cost option to scale to a reliable, high-performance transaction-based server. The Advantage Local Server is 100% API (application programming interface) compliant with the server version, though it lacks some of the features that only a database server can provide, such as support for true transactions. As is obvious at this point, we are big fans of Advantage. We use it, many of our clients use it, and we are nothing short of grateful for its combination of power and usability. And with the features added in ADS 9, the Advantage team has added real value while preserving the features that have made it the developer friendly server that has earned loyalty since the early 1990s. But if you are an Advantage user, you already know all this. So let’s get to the essence of this article, the new features added to Advantage Database Server 9. Windows and Linux Servers Available in 64-bit Versions Advantage Database Server is now available in 64-bit versions for both its Windows and its Linux versions. Not only does the 64-bit version execute faster, but it also leverages the ability to address much more memory than 32-bit versions. It is well known that 32-bit operating systems can address a maximum of only 4 gigabytes (GB) of memory. In fact, the 32-bit version of Advantage can use a maximum of 3 GB of memory on
DATABASES 32-bit processors, though it can use 4 GB on 64-bit processors (with the right configuration). The addressable memory on a 64-bit operating system has a theoretical limit of 16 exabytes (that’s 16 billion gigabytes, or 16 million terabytes, a very large amount of memory no matter how you measure it). On a practical scale, current 64-bit operating systems support much less RAM. For example Vista 64-bit Home Premium supports 10 GB of RAM, while XP Server 2003 R2 Enterprise Edition can address up to one terabyte (1024 gigabytes). The operating system is not the only factor. CPU and motherboard
Not only does the 64-bit version execute faster, but it also leverages the ability to address much more memory than 32-bit versions design also affect the upper limits of RAM available to Advantage. Nonetheless, the 64-bit version of Advantage (which is designed for the x86 processor) can make use of much more RAM than the 32-bit version. And the more RAM a database server can access, the more it can cache tables and indexes, permitting it to provide faster operations to more users. In addition to the 64-bit option, the Advantage team has streamlined its server offerings. Previously, there were separate North American and international versions the Advantage server. These have now been combined. Collation sequences previously only available in the international version are now available to North American developers as well. ARC Enhancements: A SQL Debugger and More A number of valuable updates can be found in the Advantage Data Architect (ARC), the interactive Windows application that developers can use to inspect, design, configure, and edit table and database definitions. Of all the updates to ARC, the single most exciting is the inclusion of a SQL debugger within the SQL Utility. The SQL Debugger The SQL Debugger is a full-featured debugger, providing you with breakpoints, variable inspection, and call stack tracing. Using the SQL Debugger, you can quickly debug SQL scripts, SQL stored procedures, triggers, views, and user defined functions. The SQL Debugger supports running a script directly in debug mode by pressing Ctrl-F5 or clicking the Debug button in the SQL Utility toolbar. Alternatively, you can set a breakpoint and run the script normally by pressing F5 or clicking the Run button in the SQL Utility toolbar.
When run normally, the debugger will break and suspend execution upon reaching the breakpoint. Once in the debug mode, you have a number of options. You can step through individual statements, step into or over stored procedure and user defined function calls, step out of function calls, or continue running to the next breakpoint. Figure 1 shows a user defined function (UDF) being debugged in the SQL Utility’s SQL Debugger. For SQL stored procedures and user defined functions, you can also enter the debugger by right-clicking the stored procedure or function in the Connection Repository and selecting Debug/Test, as shown in the following illustration. In this mode, changes that you make to the stored procedure or function are saved directly to the data dictionary, saving you time.
While the debugger is active, variables, the call stack, and breakpoints are displayed in individual panes, as you can see in figure 1. You can drag-dock these panes within the SQL Utility, or if you prefer, you can drag them outside of the SQL Utility as floating panes. Either way, these customizations permit you to configure the debugger to best suit your needs. SQL Utility Usability Enhancements While the SQL Debugger is undeniably the most significant addition to the Advantage Data Architect, there are many additional convenient enhancements. In addition to the debugger, the SQL Utility now sports a tabbed interface, permitting you to open multiple scripts in a single window. Another small addition, but one that is very welcome, is the implementation of text search along with find and replace features within the SQL Utility. Non-Modal Dialogs in ARC The Advantage Data Architect now also makes more extensive use of non-modal dialogs for many of its configuration features. Gone are the many modal dialogs that required you to finish a task before beginning another. For example, while changing a table structure, you can view the structures of other tables, create a stored procedure, and configure a view, all at the same time. Windows Themes and Automatic Update Detection Additional enhancements include support for Windows themes and the automatic detection and installation of updates. In short, the Advantage Data Architect, which received a complete rewrite with the release of ADS 8.0, is maturing nicely, providing you with the power and ease of use you’ve come to expect from the Advantage server itself.
Fig. 1: Using the SQL Debugger in the SQL Utility
Additional enhancements include support for Windows themes and the automatic detection and installation of updates magazine voor software development 29
DATABASES Improved Client Behavior with Notifications With each major released of Advantage, ADS gains one or more impressive new features associated with enterprise-level database servers. Advantage 6 introduced stored procedures, users and groups, and data dictionaries. Advantage 7 added triggers to the mix, while Advantage 8 provided replication and online backup services. And now, ADS 9 adds support for notifications. Notifications provide a mechanism for communicating to a client that something has occurred in the database. For example, notifications can be used to inform a client application that data in a critical table has changed. The client application can then use this information to refresh its view of that table, providing the end-user with the most current data. Or a notification might be used to signal a client that a record has been added to a special table created for the purpose of communicating messages from the system administrator to the end users. The client can then read the latest message and display it within its interface. Client applications subscribe to an event by calling the sp_CreateEvent system stored procedure, to which it passes a string identifying the event of interest. The events themselves are created with calls to the system stored procedure sp_SignalEvent, and can only be executed from within a stored procedure or trigger. Like sp_CreateEvent, sp_SignalEvent is passed a string that corresponds to events that one or more clients are expected to subscribe to. The call to sp_SignalEvent is passed an additional parameter that determines whether subscribing clients should be signalled immediately, or only after the current transaction is committed (assuming that the call to sp_SignalEvent was initiated within a transaction), permitting the database to signal events that might be rolled back, in which case, the client will not receive the notification. Clients receive an event by calling either sp_WaitForEvent or sp_WaitForAnyEvent. Both of these procedure calls are synchronous, or blocking. Specifically, the call to either of these methods will not return until an event is signalled by the database, or the call times out. The timeout, which is specified by a parameter passed to the wait procedures, can either be infinite, or limited to a specified number of milliseconds. (Note that the wait call times out immediately if the database has already signalled the event since the last wait call.) Because sp_WaitForEvent and sp_WaitForAnyEvent are blocking calls (the call does not complete until a signal is received or the timeout expires, whichever comes first), most developers who need real-time notifications from the database will employ multithreaded techniques. Here is an example of a typical notification scenario. Before a client application begins displaying a table that must always display the most current data to the user, the client will subscribe to an event associated with updates to that table. The client does this by calling sp_CreateEvent, passing the string you have created to uniquely identify the event. On the server, you create AFTER INSERT, AFTER UPDATE, and AFTER DELETE triggers on the underlying table. From these triggers, you call sp_SignalEvent, again using the same string that the client applications use to subscribe to the event. As soon as one of the client applications begins displaying the critical data, the client creates a new thread that then calls either sp_WaitFor Event or sp_WaitForAnyEvent. Code that immediately follows the call to the wait procedure typically tests whether the call has timed out or has been signalled, which can be determined by the values returned by this stored procedure call. If the call was signalled, the thread will attempt to lock a synchronization object and update the display of the data. This synchronization object must also be shared with the thread responsible for the user interface, in order to prevent the multiple threads from interfering with each other. Once the display has been updated, the notification thread releases the synchronization object and loops back to another call to the wait procedure.
30
MAGAZINE
While this type of programming requires careful consideration of shared resources, such as the user interface elements that display the data, when done properly, any signals received by the clients can be reacted to immediately as opposed to enduring the delays inherent with polling (the period check for database updates). Like ADS, ALS also supports notifications. However, due to the local server nature of ALS, and the lack of a centralized service, ALS notifications are much less responsive than those supported by ADS. Dynamic Database Configuration The Advantage Database Server has always been a low-maintenance solution. In most cases, you rarely need to adjust your database settings following your initial installation. For example, as a developer you may have determined that your typical installation requires 250 connections, 750 work areas, 100,000 data locks, and 150 tables. In the past, if you did not configure enough resources, for example, and you did not allocate enough connections, some connections would have been rejected. The remedy in those cases was to increase the number of connections. While easy enough to do, this required manual intervention and a restart of the server. With ADS 9, the configurable parameters on the Advantage Configuration Utility, shown in figure 2, are merely default maximums. (Linux and Novell NLM servers use a configuration file to configure default database settings.) If more resources are required than configured, ADS 9 automatically allocates the additional resources. For example, imagine that you’ve configured your server to support 100 tables, but in reality, your application needs 200. When ADS 9 first encounters the need for more than 100 table handles, it automatically increases the resources available for tables. If the number of required resource later decreases, ADS 9 can release these additional resources, making additional memory available for other needs.
Fig. 2: The Advantage Configuration Utility Group Default Privileges In order to simplify the process of granting privileges to database objects, ADS 9 introduces four new default groups. The DB:Admin group has the same rights as ADSSYS, with the sole exception of changing the ADSSYS password. DB:Backup provides permissions to execute backup and restore system stored procedures. Similarly, the DB:Debug group has permission to debug SQL scripts, as well as rights to modify SQL stored procedures, user defined functions, and triggers. The privileges of these three default groups are not customizable. In other words, the privileges conferred by these default groups are specific to the tasks associated with users whose rights match the intent of these groups. By comparison, the fourth group, DB:Public, is completely customizable. Furthermore, all users are by default members of the DB:Public group, although this group membership
DATABASES can be selectively revoked for individual users. The purpose of the DB:Public group is to define a default set of privileges that most users should inherit. For example, by giving insert, update, and modify privileges to a particular table to the DB:Public group, you are in effect granting these rights to any new users you add to the data dictionary (although these rights can be revoked for individual users, if necessary). Support for Visual FoxPro Developers While ADS has provided support for FoxPro tables and CDX/IDX indexes for a long time, ADS 9 has added full support for Visual FoxPro tables, including auto-increment, date/time, and VarChar fields. Advantage can use its proprietary, high-performance locking with Visual FoxPro tables, but also supports a compatibility mode, permitting legacy applications that access the Visual FoxPro tables directly to co-exist with newer, Advantage-based applications. Advantage adds the new ADS_VFP table type for this support. To further assist Visual FoxPro developers with migrating their applications to ADS 9, Advantage includes a special tool called the Visual FoxPro Conversion Utility. This utility, which is a Visual FoxPro application, can import most of the information from a Visual FoxPro Database Container (.DBC) into an Advantage Data Dictionary, including field and record constraints, referential integrity rules, default values, views, and primary keys. In addition to the Visual FoxPro table support, ADS 9 includes a number of enhancements to its support for traditional DBF files. Now, when DBF files are used with a data dictionary, they gain support for default field values, as well as minimum and maximum field constraints. And when the new Visual FoxPro file format is used, indexes can be based on binary concatenation, simplifying the creation of multi-field indexes based on different data types, as well as permitting the inclusion of null-values to be part of an index expression, without forcing the whole expression to evaluation to null. Further Support for Delphi Developers Delphi developers have always enjoyed a very high level of support for database development using ADS, making it a favorite database server for Delphi applications (and a perfect replacement for the Borland Database Engine). ADS 9 continues this tradition with the introduction of the TDataSet Switching Utility. Delphi developers who distribute vertical market applications sometimes need to support several versions of the Advantage Database Server, meaning that they need to compile their applications using different versions of the TDataSet Descendant. For these developers, the TDataSet Switching Utility, which is installed with ADS 9 version of the TDataSet Descendant (Delphi 3 – 7) and Advantage Delphi Components (Borland Developer Studio and CodeGear RAD Studio), automates the update of the version of the TDataSet descendant installed in their IDE (integrated development environment), including the path to the Advantage Client Engine DLLs. In addition to the TDataSet Switching Utility, ADS 9 also provides an Advantage Delphi Components installer for RAD Studio 2007. General Improvements to SQL Performance, Including Transactions In general, SQL performance has been improved in almost every area of Advantage SQL. These improvements are representative of Advantage’s commitment to providing Advantage developers with a first class SQL engine that just happens to be based on navigational ISAM. The improvements to overall SQL performance are significant. The Advantage team testing has found that version 9 showed 9-20% SQL performance improvements over the previous version. Improvement is most pronounced with large tables (one million or more records). General SQL improvement is due to the optimization of multi-segment AOFs (Advantage Optimized Filters) that are used extensively by the Advantage SQL engine.
In addition, transactions are also improved, particularly when those transactions involve thousands of updates.
In general, SQL performance has been improved in almost every area of Advantage SQL VarChar and VarBinary Fields in ADT Tables The Advantage ADT proprietary table type has also received an upgrade. ADT tables now support two variable length fields, VarChar and VarBinary. These fields, which unlike memo and binary fields, are stored in the ADT table file, rather than the memo file. These field types support variable length data up to 64K in size. Backup and Restore AEPs and Triggers While the ability to backup and restore Advantage databases was a significant addition to version 8, you did have to back up non-SQL Advantage Extended Procedures (AEPs) and triggers manually (those based on DLLs, COM objects, or .NET assemblies). Version 9 now backs up and restores these files external to the data dictionary. In addition, a new backup and restore option, TableTypeMap, permits you to indicate the table type of each of your free tables, which is useful if your application uses a mix of free table types, such as a combination of DBF and ADT tables. This feature is not necessary if all of your free tables are of the same table type. Support for the SQL MERGE Statement ADS 9 also introduces support for the SQL MERGE statement. MERGE provides you with a flexible statement for inserting or updating records in a table, all in a single statement. For example, MERGE permits you to perform the equivalent of an UPSERT. In an UPSERT, records from a source table are updated in a destination table, if the source records match destination table records based on a WHERE clause. Where no matches are found, source records are inserted into the destination table. Without MERGE, the equivalent of an UPSERT requires two, somewhat more complicated SQL statements. Miscellaneous Advantage Enhancements As mentioned at the outset, we are not attempting to cover every updated feature in ADS 9. While many of the features we are overlooking may be particularly important to certain developers, we have tried to focus on those features that will be of interest to the greatest number of developers. Nonetheless, there are additional updates and enhancements that deserve mention, though space considerations do not permit us to feature them in their own section. Additional enhancements to Advantage Database Server 9 include: • Native drivers for Crystal Reports v11 R2 and Crystal Reports 2008 • Native drivers for Visual Objects AXSQL RDDs • Full Text Search enhancements (non-English enhanced search functionality, and CONTAINS AND searches across multiple fields) • Significantly improved performance on re-index operations • The ability to pause replication subscriptions on a subscriptionby-subscription basis • Merge functionality added to replication • Support for variable length expressions • An option for flipping the direction of an index at runtime (from ascending to descending, or visa versa) • An AdsSkipUnique API added for table navigation, permitting to you seek to the next unique value in an index
magazine voor software development 31
DATABASES Conclusion Advantage Database Server 9 includes significant performance improvements and important feature enhancements, continuing Advantage's history of invaluable improvements over the years. The most amazing aspect is while the Advantage team has added advanced features and excellent support for a wide variety of development environments, they’ve kept true to Advantage’s promise of simplicity of use and low maintenance •
Cary Jensen and Loy Anderson
Noteren in agenda: 6 & 7 oktober a.s.: SDN Conference
Cary Jensen and Loy Anderson are award-winning authors of 20 books, including Advantage Database Server: A Developer’s Guide (2007, Sybase) and Advantage Database Server: The Official Guide (2004, McGraw-Hill Osborne Media). Cary and Loy are principles at Jensen Data Systems, Inc., a consulting, training, technical writing, and software development company. Cary specializes in database development and software developer training, and Loy focuses on help system design, writing user documentation, and quality assurance testing. Both Cary and Loy have Ph.D.s in Human Factors Psychology from Rice University. For more information about Jensen Data Systems, including Advantage training, visit http://www.JensenDataSystems.com.
Advertentie 4DotNet b.v.
32
MAGAZINE
• •
35 sprekers (nationaal & internationaal), 100 sessies value for money!
SDN CONFERENCE
2 DAYS SOFTWARE DEVELOPMENT CONFERENCE CONFERENCE.SDN.NL
6 & 7 oktober 2008 Leeuwenhorst, Noordwijkerhout Tijdens SDN Conference 2008 wordt tevens de DotNetNuke OpenForce ’08 Europe conferentie gehouden. Deelnemers aan de SDN Conference hebben gratis toegang tot alle OpenForce sessies.
Conferentiecentrum Leeuwenhorst, Noordwijkerhout .Net • Delphi • User eXperience • Information Worker • Architecture • DotNetNuke • Databases
EEN ALL-IN PRIJS Het SDN is een No Nonsense Club, dus geen “verrassingen” achteraf. De prijs is duidelijk en “All-In”, dus mét consumpties in de pauzes, mét lunch (2x) en mét diner op maandagavond. Zelfs het parkeren is inbegrepen! Wie wil overnachten in het Leeuwenhorst hotel (een aanrader!) kan bij het aanmelden meteen een hotelkamer boeken. Wees er wel snel bij want het aantal beschikbare kamers is beperkt.
SDN leden betalen € 695,- voor deelname, niet-leden betalen € 795,Meer informatie en aanmelden: conference.sdn.nl
S o f t wa r e D eve l o p m e n t N e t w o r k , Po s t bu s 5 0 6 , 7 1 0 0 A M W i n t e r sw i j k
Te l : 0 5 4 3 - 5 1 8 0 5 8
w w w. s d n . n l
overzicht sprekers en sessies .NET Maurice de Beijer Brongers / Hessing Pieter de Bruin Richard Campbell Miguel A. Castro
NL NL NL CAN USA
Chad Z. Hower
CAN
Julie Lerman
USA
Beth Massi
USA
Mark Miller Brian Noyes
USA USA
Oliver Sturm
UK
Alex Thissen Thissen / Gielens Marcel de Vries Shawn Wildermuth
NL NL NL USA
Windows Workflow Foundation best practices One Hour Code And Fun Introductie in Enterprise Library 4 From One Web Server to Two: Making the Leap Designing Applications for Extensibility WCF the Manual Way... the Right Way Silverlight Web Controls… yes you heard right Cosmos - Running .NET without Windows .NET: Using the Wii Remote to enhance user interfaces Deep Dive into Entity Framework Object Services Introducing the Entity Framework Conquering XML with LINQ in Visual Basic 9.0 LINQ to Everything Window Presentation Foundation Internals WPF Data Binding WPF in Windows Forms and Vice Versa Handling data in F# Taking efficiency one step further - F# Introduction to ASP.NET MVC Technology Deathmatch: LINQ to SQL vs. ADO.NET Entity Framework What is new in visual Studio 2008 & SP1 Inside ADO.NET Data Services
Delphi Marco Cantù
IT
Pawel Glowacki
NL
Hadi Hariri
ES
Nick Hodges
USA
Cary Jensen
USA
Jeroen Pluimers
NL
Bob Swart
NL
Generics and Closures in Delphi Multi-Threading in Delphi Towards a Delphi REST Framework What's New in Delphi Tiburon Database Programming Deep Dive into Delphi Unicode support Advanced VCL for the Web Unit Testing in Delphi Programming in Delphi doesn’t imply OOP Delphi Product Address What's new in Delphi Tiburon What's cooking (Hodges, Glowacki, Swart) Fundamentals of ADO.NET for Delphi Developers Fundamentals of Web Development with ASP.NET and Delphi 7 Cool ADO.NET Techniques for ASP.NET Applications Nullable types in Delphi met records, methods en operator overloading Waar moet je op letten met XML voor Data Import en Export Delphi Tiburon's Compiler and Language Enhancements
User eXperience Antoni Dol Serge Jespers
NL BE
Julie Lerman Mark Miller Christoph Rooms
USA USA BE
Shawn Wildermuth
USA
6 & 7 oktober 2008 Leeuwenhorst, Noordwijkerhout MAGAZINE
Deep, Deeper, Deepest: 3D for UI in WPF Adobe Flex for Designers and developers Inspire Session: Building AIR with HTML/AJAX Annotation in Silverlight 2.0 The Science of Great UI Building Data Rich Internet Applications Adobe Flex en AIR : De kracht van Rich Internet Applications Controls Customization in Silverlight 2
CONFERENCE.SDN.NL
overzicht sprekers en sessies Information Worker Brongers / Hessing Tim Huckaby
NL USA
Beth Massi Mirjam van Olst
USA NL
Joel Semeniuk
CAN
Dennis Vroegop
NL
Building Silverlight Webparts For SharePoint Building Powerful Office Applications the Easy Way with Visual Studio Tools for Office (VSTO) Integrating WPF & WCF into Your Office Business Applications Building Powerful Visio and PowerPoint Solutions in .NET with VSTO Taking Advantage of LINQ and Open XML in Office 2007 Exploring the architectural boundaries of MOSS MOSS Development back to the basics Using Excel, the BDC, Dashboard Web Parts, and TFS Developing Excel Services Dashboard Solutions Leuker kunnen we het wel maken, makkelijker ook!
Architecture Dennis Doomen
NL
Paul Gielens
NL
Brian Noyes
USA
Marcel de Vries Shawn Wildermuth
NL USA
Building a Service Oriented Enterprise System Part 1: Introduction Building a Service Oriented Enterprise System Part 2: Best Practices Chalk & Talk: Understanding the Entity Framework’s Architecture and its Implications on your Application Architecture Build Composite UI Applications with CAB and SCSF Developing Service Oriented Workflows Creating Scalable Statefull services using WCF and WF Understanding Visual State Manager in Silverlight 2
Databases Peter ter Braake Campbell / Forte Joachim Dürr Stephen Forte Chad Z. Hower
NL CAN/USA DE USA CAN
SQL Server 2008 – Beyond Relational TSQL Tips and Tricks (2008 Edition) Navigational Data Access in .NET Rock Star Common Table Expressions (SQL 05/08) SQLCLR vs T-SQL - When to use which one?
USA/CAN NL USA NL NL USA
Managing remote teams using Scrum How to keep our jobs Developing Scalable Apps with Google AppEngine Windows Home Server Demo Mania Domain Driven Design in .NET: Practical case Data in the Cloud
General Forte / Semeniuk / Caron Sander Hoogendoorn Nik Kalyani Tony Krijnen Pieter Joost van de Sande Shawn Wildermuth
DotNetNuke OpenForce ‘08 Europe Erik van Ballegoi Cathal Connolly
NL UK
Stefan Cullmann
DE
Christopher J. Hammond
USA
Nik Kalyani
USA
Stefan Kamphuis Sebastian Leupold
NL DE
Charles Nurse
CAN
Ernst Peter Tamminga Shaun Walker
NL CAN
Create a DotNetNuke module using Delphi.NET Building Secure DotNetNuke installations Using Web 2.0 APIs in DotNetNuke modules Create a DotNetNuke skin from a website design DotNetNuke Module Applications Introduction into Forms & Lists (UDT 5.0) DotNetNuke Installation, Administration and Optimization Techniques for Cambrian Friendly URLs and SEO best practices for websites (DNN Slant) Developing a consulting business around Open Source software. Skinning Enhancements in DotNetNuke 5 Developing Vista Sidebar Gadgets Using DotNetNuke DotNetNuke Chalk & Talk Using Mail and Messaging Services in DNN5 How to provide and identify Quality of DotNetNuke extensions? Test Driven DotNetNuke Module Development A Deep Dive into the latest version of DotNetNuke A Deep Dive into the DotNetNuke Property Editor DotNetNuke framework: a hidden juwel? DotNetNuke 5.0 DotNetNuke Chalk & Talk
GENERAL
Interesting Things:
Core Het is maandagmorgen. Er schijnt een waterig zonnetje over Ede. Ik parkeer mijn auto op het parkeerterrein van de Reehorst dat verlaten oogt bij de start van de nieuwe werkweek. Ook vandaag is de Reehorst het toneel voor een Software Developer Event. Één sessie in het bijzonder heeft mijn interesse: Addressing Non-Functional Requirements with Aspects. Waarom nu juist deze sessie? Het verhaal gaat over PostSharp, een aspect oriented framework, en wordt gegeven door Gael Fraiteur, de bedenker van het framework. PostSharp is geschreven in C# en is een hele cleane, voor reguliere developers begrijpbare implementatie van aspectoriëntatie. Nadat ik in mei op een conferentie in Zurich al een presentatie over PostSharp had bijgewoond, schreef ik op een terras meteen al mijn eerste aspect. Heerlijk eenvoudig! Frameworks. Ik kan er niets aan doen. Ik hou gewoon van frameworks. Heerlijk om te zien hoe developers, teams en project hun generieke problemen oplossen. Of eigenlijk, zoals ze keer op keer dezelfde problemen oplossen. Want wie maakt bijvoorbeeld het id aan van een nieuw domeinobject? De applicatie of de database? Trechter Soms levert het kijken naar code uiterst aangename middagen op, zoals bij een recente code audit die ik samen met een collega uitvoerde op een enterprise portal voor een wereldwijd bekend merk. Laat ik het anoniem houden. Een schoolvoorbeeld van hoe het niet moet. Meer dan een miljoen regels code. De ontwikkelaars hadden getracht een servicegeoriënteerde softwarearchitectuur te implementeren. Waarschijnlijk op aanraden van de architectuurafdeling van het bedrijf. “Wij ontwikkelen hier altijd onder architectuur. Een servicegeoriënteerde uiteraard.” In deze portal betekende dat dat alle bedrijfsfuncties vanuit de webpagina’s als webservice werden aangeroepen. Op zich nog niet verkeerd, al gaf het deploymentmodel in te uitgebreide documentatie geen aanwijzingen voor de noodzaak hiervan. Bijzonder fraai was dat al deze services – 354 in totaal – allemaal op een en dezelfde klasse waren geschreven. Met recht een trechter. Vorige week verzorgde ik in Antwerpen een tweedaagse training in het pragmatisch toepassen van UML. Ik kom graag in Antwerpen. Fijne terrassen, prima bier. En zo belandde ik aan het eind van een hectische eerste cursusdag op een terras op de Melkmarkt met twee cursisten. Beiden werken voor een klein bedrijf in Utrecht dat software ontwikkelt voor zorginstellingen en ziekenhuizen. Deze software, zo verhalen de twee functioneel ontwerpers, wordt ontwikkeld in PHP. Ooit gestart door twee techneuten met een goed idee voor een onontgonnen markt en voldoende kennis van PHP is het bedrijf nu vijftig man groot. Maar bij de start gebeurt het! Je schrijft je eerste code en ontdekt dat sommige software herbruikbaar kan zijn. “Hé, we hebben een object Patient en een object Operatiekamer en die worden beide opgeslagen. Laten we dat isoleren.” En voor je het weet ontwikkel je je eigen framework. Met alle bijzondere principes en eigenaardigheden vandien. Jaloers Het ontwikkelen van frameworks vergt een heel eigen vocabulaire, patterns en best practices. Al eens naar het patroon layer supertype
36
MAGAZINE
gekeken? Ook model-view-controllers zijn er in overvloed. En gebruikt niet ieder project een object relational mapper? En of dat nu Microsoft’s Entity Framework, Hibernate of wellicht een eigen implementatie is, alles is mogelijk! En wellicht maken uw projecten gebruik van dependency injection – het mechanisme waarmee pas runtime afhankelijkheden worden gelegd. Gebruikt u ObjectBuilder of zijn opvolger Unity? Of dan toch liever open source? Probeer Windsor. Ook bij Microsoft lusten ze wel pap van het schrijven van frameworks. Microsoft – of meer specifiek Scott Guthrie – heeft zich onlangs weer van zijn beste kant getoond. De beta van Silverlight is de deur nog niet uit, of Scott verlustigt zich al aan zijn volgende framework, getiteld ASP.NET MVC. Soms ben ik daar stiekem jaloers op. Op de ontwikkelaars van het framework in PHP in Utrecht die daar een op ziekenhuizen gerichte eigen variant van Outlook mee ontwikkelen, op Gael Fraiteur met zijn PostSharp, maar ook op Scott Guthrie en de andere bedenkers van frameworks bij Microsoft. Catchy Er is namelijk niets leukers dan het schrijven van een nieuw framework. Gewoon van scratch af aan. Verzin een catchy naam: Django of Zend. Start met een nieuwe solution in Visual Studio en maak je eerste items aan. Zou ik nu starten, dan zou ik eerst een assembly Core en een assembly Domain aanmaken. En dan een interface IDomainObject definiëren en een klasse DomainObject die dit implementeert. Met een property Id schat ik in – een value object uiteraard – en een property Title, zoals ook het NakedObjects framework doet voor het eenvoudig representeren van domeinobjecten. En voor de zekerheid overschrijf ik ToString() zodat deze dan mooi Title retourneert. Misschien begin ik vanavond nog wel! Maar ook voor frameworks geldt: er is een tijd van komen en een tijd van gaan. Op een gegeven moment is de rek uit je framework. De ooit briljante ideeën zijn ingehaald door nieuwere frameworks, die hipper zijn, open source, en uitgebreid gedocumenteerd. Wat te doen? Een complexe keuze. Ontwikkel ik verder aan mijn eigen framework? Implementeer ik daarbij nieuwe features die andere frameworks al kennen? Hoeveel aanpassingen moet ik daarvoor maken? Of ontwikkel ik een nieuw framework, dat al deze nieuwe features al standaard in zich heeft, en natuurlijk beter is dan wat de markt nu te bieden heeft. Of stop ik helemaal met het opzettten van frameworks, en ga ik alleen gebruik maken van frameworks van anderen? Of stoten we meteen maar al het ontwikkelwerk af naar India? Laat ik me er voorlopig maar niet over buigen. Een tijd van gaan? Inderdaad. Tijd om Word af te sluiten en Visual Studio te openen.
Sander Hoogendoorn blog.sanderhoogendoorn.org •
UX
Robertjan Tuit
Silverlight2 MSN Video ListBox There is a lot of development techniques and functionality that we are only used to have on the desktop. But with Silverlight2 (SL2) a lot of these have come to the browser. One of these techniques is data binding, not the "fake" data binding done in ASP.Net, but the real deal. In this article I'm going to touch a little styling and templates as well. To show you the power of data binding in SL2 we are going to create a ListBox filled with images and videos from the MSN Video website. I will walk you through every step of the way, so at the end you will know how to do it yourself. And just to make it a little bit interesting, this is what the end result will look like, although the exact pictures will be different: As I said we will be using the MSN Video Service as our data source; this means that you need a live internet connection when building the application. The other requirements in order to be use the information in this article are: • Visual Studio 2008 SP1 • Silverlight2 Beta 2 Tools for Visual Studio 2008 SP1 • Silverlight2 Beta 2 SDK • .NET Framework 3.5 • .NET Framework 3.5 SP1 Beta • Blend 2.5 2008 June Preview
In the next screen select "Add a new Web to the solution for hosting the control" and select "Web Application Project"; name it MSNVideoListBox.Web. After the two projects have been, set MSNVideoListBox.SilverlightTestPage.aspx as your start page. Your solutions file should be looking like this:
Before we really begin, some of us (lazy like me) like to have the complete source code upfront, so click here to download the complete source code. New Project We start out by creating a new Silverlight Project named MSNVideoListBox.Silverlight. Also create a new solution named MSNVideoListBox, like in the picture below.
Getting the Video Feed Now that we have set up our solution, let's get some real data. The URL from which we can get 10 random videos from MSN Video is http://catalog.video.msn.com/randomVideo.aspx?mk=us&vs=0&df=9 9&c=10. You can just open it in your browser to see the XML it returns. First we have to set up the code that is going to get the feed. Create a new class file in the Silverlight project called VideoDS.cs and let the class use INotifyPropertyChanged (requires System.ComponentPanel in the using directives). Implement it, add a private constant string named _RandomVideoUrl and set it to the Url. Then add a public method LoadVideos. You code should look like this: public class VideoDS : INotifyPropertyChanged { private const string _RandomVideoUrl = "http://catalog.video.msn.com/randomVideo.aspx"+
magazine voor software development 37
UX ns + "description"),
"?mk=us&vs=0&df=99&c=10"; public ObservableCollection RandomVideos
ImageUrl = GetUriAsset(video.Element(ns + "files"),
{get; set;}
"file", "2007", ns),
private void LoadVideoFeed() { }
VideoUrl = GetUriAsset(video.Element(
#region INotifyPropertyChanged Members
ViewCount = (string)video.Element(ns + "usage").
ns + "videoFiles"), "videoFile", "1002", ns), Element(ns + "usageItem").Attribute("totalCount")
public event PropertyChangedEventHandler PropertyChanged; };
#endregion
return videos.ToList();
} }
In the LoadVideoFeed method we are going to use a WebClient object to get the feed. Remember that all network classes in SL2 work asynchronously. Luckily we have C# 3.0 at our disposal, so we can use one of my favorite new language features, lambda expressions, to handle the event cleanly. Also add System.Net to the usings.
As you might have noticed, the Image and Video Url need a little more work. The following method does all the hard work for us. private string GetUriAsset( XContainer element, string nodeName, string formatCode, XNamespace ns)
var wc = new WebClient(); wc.DownloadStringCompleted += (sender, e) =>
{ var uris = from file in element.Descendants(ns + nodeName)
{
where (string)file.Attribute("formatCode") == formatCode
//TODO: Handle the Result
select (string)file.Element(ns + "uri");
};
return ((uris.Count<string>() > 0) ?
wc.DownloadStringAsync(new Uri(_RandomVideoUrl));
uris.First<string>() : string.Empty);
Now that we have the feed, we need to parse it. Here we will be using another nice new language feature: Linq to Xml. But first we need to create a class in which we can put the data we will parse from the XML document. Create a new class file in the Silverlight Project named VideoFeedItem.cs and paste the code below. public class VideoFeedItem { public string Title { get; set; } public string Source { get; set; } public string PublishDate { get; set; } public string Description { get; set; }
}
So now that we have all our methods set up, let's go back to the LoadVideoFeed method and finish up. We use the GetVideoFeedItemsFromXmlString to convert the string to a collection and then add them to an ObservableCollection. The ObservableCollection will handle all the notifications to bound controls. When you bind a control to an observable collection or any other object that implements the INotify-PropertyChanged interface, it will register to an event that fires whenever the object is changed. In this case we need to send that signal ourselves by calling the PropertyChanged method. This will inform our bound ListBox that it can get new data.
public string ImageUrl { get; set; } public string VideoUrl { get; set; }
wc.DownloadStringCompleted += (sender, e) =>
public string ViewCount { get; set; }
{
}
var feedItems = GetVideoFeedItemsFromXmlString(e.Result); RandomVideos = new ObservableCollection();
Back to VideoDS.cs. Add a new method GetVideoFeedItemsFromXmlString(string Xml) with a List return type; this will require you to add System.Collections.Generic as a using. In the method we are going to create a new XDocument with the contents of the string. Then we use Linq to Xml to parse out all the items we need. First add a reference to the System.Xml.Linq assembly and add it as a using. Also add System.Linq to the usings. Because the XML uses namespaces, we also need to add this to every node we are trying to access. The resulting code looks like this: public List
foreach (var feedItem in feedItems) RandomVideos.Add(feedItem); PropertyChanged(this, new PropertyChangedEventArgs( "RandomVideos")); };
Now all we have to do is call the LoadVideoFeed method from the constructor. The HtmlPage.IsEnabled will make it only work in runtime, otherwise it would also try to do it in design time. public VideoDS()
GetVideoFeedItemsFromXmlString(string Xml)
{
XNamespace ns =
}
{
if (HtmlPage.IsEnabled) LoadVideoFeed(); "urn:schemas-microsoft-com:msnvideo:catalog"; var doc = XDocument.Parse(Xml);
So that wraps up most of the backend code.
var videos = from video in doc.Descendants(ns + "video") select new VideoFeedItem() { Title = (string)video.Element(ns + "title"), Source = (string)(video.Element(ns + "source").Attribute("friendlyName")), PublishDate = DateTime.Parse((string)video.Element( ns + "startDate")).ToShortDateString(), Description = (string)video.Element(
38
MAGAZINE
Binding Now that we have setup the classes necessary for the data loading and binding, let's add the ListBox to our PAGE.XAML, and bind it to our data source. We can do this manually by typing in XAML but I prefer using Blend. Open up the page in Blend by right clicking PAGE.XAML and selecting "Open in expression Blend".
UX Now we can bind the ListBox to the data source by dragging the VideoDS item in the panel to the ListBox. You will have to open it up first and drag the RandomVideos item onto it. When you drop it on the ListBox a menu will pop up where you select "Bind VideoDS to ListBox".
In Blend, first select the root [UserControl]. Go to the Properties tab and change the size to 600 by 200 pixels. Then a window will pop up where you select the ItemsSource property to bind to. Press OK the create the binding. If we go back to Visual Studio and run the application, the result will look similar to this:
Add the ListBox, by selecting it in the asset library and then double clicking it in the toolbar.
Template As you see here, the ListBox has found our collection but has no clue whatsoever what to do with it. To tell it how it should display our data, we add ListBox.ItemTemplate and we change the orientation using the ListBox.ItemsPanel property: When you add it this way it defaults to 100 by 100 pixels. We want it to fill the the canvas so we remove the sizes by clicking on the small square next to the properties and selecting reset or the button next to it to set it to auto. And change the aligment to stretch both ways.
Go back to the project tab, and click +CLR Object. This will open up a window where you enter the name VideoDS and select the VideoDS object we created to make it a data source. It will add the VideoDS to the Data panel.
<StackPanel Orientation="Horizontal"/>
This should result in the following, with other data of course since it is random:
The latest version of Blend has tooling built in to do this visually, so a designer could change the look of the items in the ListBox without the need for XAML. Style I'm always a big fan of the separation of design and behavior, so let's move the design to a style. First remove the properties and add a Style
magazine voor software development 39
UX to the ListBox: <UserControl x:Class="MSNVideoListBox.Silverlight.
And then add a Style to the pages UserControl.Resources:
VideoFeedItemControl" xmlns="http://schemas.microsoft.com/client/2007"
<UserControl.Resources> <MSNVideoListBox_Silverlight:VideoDS x:Key="VideoDS" d:Isdata source="True"/> <Style x:Key="ListBoxStyle" TargetType="ListBox">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel Width="200">
<Setter Property="ItemsPanel"> <Setter.Value>
HorizontalAlignment="Center"
<StackPanel Orientation="Horizontal"/>
Margin="10,10,10,10"/>
<Setter Property="ItemTemplate">
<Setter.Value>
And place the in the PAGE.XAML :
<Setter Property="ItemTemplate"> <Setter.Value>
<MSNVideoListBox_Silverlight:VideoFeedItemControl/>
The above style does exactly the same thing as with the properties, so no changes in result here. Image Binding Next we will add an Image to the mix and add a StackPanel and some Margin to make it look a little bit neater.
Ready to go, build and run. Let's see our end result:
<Setter Property="ItemTemplate"> <Setter.Value> <StackPanel Width="200">
Hey, wait a minute!? That is not what we were aiming for. It is empty! We did not get any errors, everything seems to be working correctly, what happened here?
Margin="10,10,10,10"/>
The fix The error is with the automatic namespace declaration that Blend created for us:
xmlns:MSNVideoListBox_Silverlight=
Build and run the project, it should look something like this:
"clr-namespace:MSNVideoListBox.Silverlight"
Add the right assembly (MSNVideoListBox.Silverlight), so that it looks like this: xmlns:MSNVideoListBox_Silverlight= "clr-namespace:MSNVideoListBox.Silverlight; assembly=MSNVideoListBox.Silverlight"
We build and now we get:
40
MAGAZINE
UX Then in the codebehind start the video and make the video visible. private void Image_MouseLeftButtonUp (object sender, MouseButtonEventArgs e) { Video.Play(); Video.Visibility = Visibility.Visible; }
That looks more like it! Video One last thing to do … Open up the VideoFeedItemControl.XAML file and add a grid and video element like this: <StackPanel Width="200">
Text="{Binding
Title}"
HorizontalA-
lignment="Center" Margin="5,5,5,0"/>
Start the application and click a few of them to enjoy the scene, also turn up your volume to get the full effect, especially if you have co-workers around. Conclusion So that was fun … I think it was anyway. I hope I have showed you some new stuff. If you have any questions or remarks please send them to me trough the contact form on my blog www.robertjantuit.nl and I will gladly answer any questions you might have on SL2 and with article. Stay in the light! •
Source="{Binding ImageUrl}" HorizontalAlignment="Center" Margin="10,10,10,10"/>
Robertjan Tuit
<MediaElement Source="{Binding VideoUrl}" Height="120" Stretch="Uniform" x:Name="Video" AutoPlay="False" Visibility="Collapsed"/>
The MediaElement is hidden and has a name Video. Add a MouseLeftButtonUp handler to the image element. If you type it in, Visual Studio will offer to create the handler automatically through IntelliSense. Right click on the event handler and click the "Navigate to Event Handler" item that is being presented.
Robertjan Tuit is zelfstandig developing usability consultant en is de track owner van de UX community binnen het SDN (Software Development Network). Zijn specialiteiten: Silverlight, APS.NET, Ajax, Agile, MVC & Volta.
AGENDA 2008 CodeCamp, MIC Barneveld ..................................................................................6 september EKON, Frankfurt ............................................................................................22-26 september SDN Conference 2008 DotNetNuke OpenForce ‘08 Europe .......De Leeuwenhorst, Noordwijkerhout 6-7 oktober PDC 2008, Los Angeles USA.............................................................................27-30 oktober TechEd Developers, Barcelona ...........................................................................3-7 november Visual FoxPro Konferenz, Frankfurt .................................................................13-15 november SDN Magazine Nr. 99 ..........................................................................................21 november SDN Event...........................................................................................................12 december Genoemde data onder voorbehoud
magazine voor software development 41
Advertentie Bergler Nederland b.v.
.NET
VISUAL BASIC
Maarten van Stam
VSTO 3.0 en Word Content Controls Word Content Controls Word Content Controls zijn op zichzelf staande controls die je naar eigen inzicht kunt aanpassen om te gebruiken in je templates en documenten. Een van de mogelijkheden is om op deze manier formulieren te maken met een verbeterde look-and-feel. De gebruiker kan door deze werkwijze worden geholpen bij het invullen van dit formulier. Als voorbeeld zou je een listbox aan je document kunnen toevoegen met een beperkt aantal keuzemogelijkheden waaruit de gebruiker zijn keuze moet maken. Ook kan tekst worden toegevoegd aan de control om de gebruiker instructies te geven bij het invullen en de control te laten verdwijnen zodra er iets is ingevuld. Middels het standaard Object Model van Word 2007 is kan er worden gecommuniceerd met de Content Controls en kun je de programmatuur laten reageren op events die door Word worden getriggered. Om functies en mogelijkheden uit te breiden is er door de VSTO ontwikkelaars naast het standaard Object Model van Word een aantal uitbreidingen aangebracht om het leven van de Office ontwikkelaar nog een stukje makkelijker te maken. Door middel van de Document Designer in Visual Studio kan op visuele wijze het document worden samengesteld. De Content Controls In Word 2007 is er een achttal Word Content Controls beschikbaar die kunnen worden teruggevonden op de Developer Tab van de Ribbon in Word. Standaard wordt deze Developer Tab echter niet getoond. Om deze toch te tonen kies je Word Options onder het Office menu (de grote ronde knop) en zet een check in de checkbox bij “Show Developer Tab in the Ribbon”. Elk van de Content Controls heeft zijn eigen specifieke eigenschappen die de control kenmerken. Zo kan de Rich Text control tekst bevatten in meerdere stijlen, en kan het zelfs andere Content Controls bevatten, een mogelijkheid die ook de Building Block Gallery en de Group Content Control kennen. Alle andere controls kunnen maar één stijl en geen andere Content Controls bevatten. Het volgende overzicht geeft inzicht in de verschillende soorten controls die er zijn. Tussen haakjes staat achter de naam van de Control de ID van de Control zoals je die kunt terugvinden in de Object Browser van Word. Rich Text (wdContentControlRichText) De Rich Text Content Control kan tekst in meerdere stijlen en andere controls bevatten. De formattering in Rich Text is bij de meeste mensen wel bekend door het gebruik van Word en Outlook. Er zijn weinig grenzen binnen dit type en de mogelijkheden zijn talrijk.
Fig. 1: Rich Text Content Control Text (wdContentControlText) De Text Content Control kan de tekst slechts in één stijl en één font opslaan. Het is binnen deze Content Control niet mogelijk om te wisselen van stijl en/of font type. Als dat een vereiste is zal gekozen moeten worden voor de Rich Text Content Control.
Fig. 2: Plain Text Content Control
magazine voor software development 43
.NET VISUAL BASIC Picture Content Control (wdContentControlPicture) De Picture Content Control is de control om een plaatje weer te geven, een beperkte Control speciaal bestemd voor dit doel.
Fig. 5: Drop-Down List Content Control
Fig. 3: Picture Content Control Combo Box (wdContentControlComboBox) De Combo Box Control bevat een Combo Box met een aantal listitems om uit te kunnen kiezen. In de Combo Box Control kan handmatig een tekst worden getypt in het invulveld van het uitklapgedeelte. Als je wilt dat er alleen kan worden gekozen uit een vaste lijst, dan is daarvoor de Drop-Down List Content Control bedacht die je hierna zult zien.
Buiding Block Gallery (wdContentControlBuildingBlockGallery) De Building Block Gallery is een speciaal soort control die het mogelijk maakt om de gebruiker te laten kiezen uit vooraf geprepareerde Building Blocks. Building Blocks zijn kant en klare tekst fragmenten of onderdelen van een document inclusief layout en stijlen die in zijn geheel (Quick Part, Autotext, e.d.) kunnen worden ingevoegd door te kiezen uit een Building Blocks gallery. De mogelijkheden en het gebruik van Building Blocks is zeer uitgebreid en het voert te ver om in dit artikel alle mogelijkheden te bespreken.
Fig. 6: Building Block Gallery Control Fig. 4: Combo Box Content Control Drop-Down List (wdContentControlDropdownList) De Drop-Down List bevat een Drop-Down List met een aantal items om uit te kunnen kiezen. Deze Content Control biedt in tegenstelling tot de Combo Box Control niet de mogelijkheid tot het invoeren van vrije tekst als keuze, maar moet er een keuze uit de gedefinieerde lijst worden gemaakt.
44
MAGAZINE
Date Picker (wdContentControlDate) De Date Picker maakt het de gebruiker makkelijker om een datum in te voeren. Met deze control kan een kleine kalender worden getoond waarmee de gebruiker door de kalender bladerend de gewenste datum kan selecteren. Wat ook belangrijk is bij deze Content Control is de formattering van de datum. Er kan worden aangegeven wat de ‘locale’ is en welke volgorde van jaar-, maand- en dagvelden is gewenst.
.NET VISUAL BASIC Door op de Developer Tab de Content Controls te tonen in Designer Mode wordt in het document de structuur zichtbaar van de gebruikte controls en zijn extra labels zichtbaar zoals bijvoorbeeld de “Group” labels. Tot zover de beschrijving van de verschillende Content Controls die de ontwikkelaar ter beschikking staan. Elk van de bovengenoemde Content Controls heeft zijn specifieke kenmerken. Het is raadzaam deze uitgebreid te bekijken. Events Zoals gebruikelijk is bij Microsoft software zijn er Content Controls Events gedefinieerd. Dit houdt in dat als er iets (een event) gebeurt met een Content Control of iets dat gerelateerd is aan een Content Control, er dan een signaal wordt gegeven over hetgeen heeft plaatsgevonden. Een eenvoudig voorbeeld is het event waarbij een signaal wordt gegeven in het geval een Content Control wordt binnengegaan (ContentControlOnEnter) of wanneer een Content Control weer wordt verlaten (ContentControlOnExit).
Zoals gebruikelijk is bij Microsoft software zijn er Content Controls Events gedefinieerd
Fig. 7: Date Picker Content Control Group (wdContentControlGroup) De Group Content Control is een speciaal soort control waarmee een aantal Content Controls kunnen worden gebundeld tot één enkele control. Op die manier ontstaat er een bundeling van controls die nu als één control worden beschouwd.
Fig. 8: Group Content Control
Door gebruik te maken van deze events kan op uitstekende wijze worden gemonitord wat er met de gebruikte Content Controls wordt gedaan en kan worden aangegeven of er andere opvolgende acties benodigd zijn voor verdere afhandeling van het document. Een goed voorbeeld is een Content Control dat deel uitmaakt van een adresblok. Op het moment dat zo’n control wordt binnengegaan kan de ontwikkelaar een TaskPane activeren aan de rechter zijde van het document om daar vervolgens een voor het invullen van dit adres een hulpmiddel te plaatsen die bij het intikken van de postcode meteen de bijbehorende plaatsnaam opzoekt. Bij het verlaten van het veld dat onderdeel uitmaakt van het adres kan de TaskPane weer worden gesloten. Een kort overzicht van beschikbare events op de Content Controls: • ContentControlAfterAdd: vindt plaats na het invoegen van een Content Control; • ContentControlBeforeContentUpdate: vindt plaats voor het bijwerken van een Content Control maar alleen wanneer de inhoud van de control afkomstig is van de Office XML data store; • ContentControlBeforeDelete: vindt plaats op het moment dat een Content Control wordt verwijderd en dit event treedt op vlak voor de verwijdering; • ContentControlBeforeStoreUpdate: vindt plaats op het moment dat XML datastore van het document wordt bijgewerkt; • ContentControlOnEnter: vindt plaats wanneer een Content Control wordt binnengegaan; • ContentControlOnExit: vindt plaats wanneer een Content Control wordt verlaten; • BuildingBlockInsert: dit event wijkt iets af van alle andere events en wordt geactiveerd na het invoegen van een Building Block. Deze events zijn de standaard events zoals deze beschikbaar zijn op het document. In Visual Studio krijg je echter een aantal extra events tot je beschikking wanneer je in de Document Designer een Content Control toevoegt en daar de Properties bekijkt:
magazine voor software development 45
.NET VISUAL BASIC
Fig. 9: VSTO Events
Fig. 10: Database Objects
Deze events komen in grote lijnen overeen met de events die op het document zijn gedefinieerd, maar kunnen nu op eenvoudige wijze worden afgehandeld per Content Control, dit in tegenstelling tot de events die op document niveau zijn gedefinieerd. Bij de op document niveau gedefinieerde events moet eerst worden uitgezocht op welk Content Control het event van toepassing is, terwijl bij de VSTO events deze per control kunnen worden uitgewerkt.
Na accepteren van de dialoogopties verschijnt in Visual Studio naast de Document Designer in de Data Sources dialoog een overzicht van de gekozen DataSet met de daarin gekozen “Employees” tabel. Door nu op een van de velden uit de View te klikken verschijnt er een DropDown List met daarin ten opzichte van voorgaande versies van Visual Studio een aantal nieuwe opties. Het is in deze versie mogelijk om bij de velden het type te geven van een van de Word Content Controls. In het voorbeeld van het “Title” veld kan gekozen worden uit de Content Controls PlainTextContentControl, RichTextContentControl, ComboBoxContentControl, DatePickerContentControl en BuildingBlockGalleryContentControl. In ons geval is het voldoende om de velden op het document te plaatsen als PlainTextControl. Dit houdt in dat er slechts één stijl en één font beschikbaar is. We houden dus de standaard door het systeem gekozen typen aan.
Data Binding Interessant wordt het pas wanneer blijkt dat de Content Controls kunnen worden gebonden aan objecten zoals Document Properties of zelfs databasevelden. Dit maakt het mogelijk om het document te vullen met informatie uit het bedrijfssysteem. Het binden aan databases kan worden gedaan in een ‘read only’ modus maar ook in een tweeweg-binding waarbij wijzigingen kunnen worden teruggezonden naar de database. Bij deze laatste mogelijkheid krijgt de ontwikkelaar van de documenten de mogelijkheid om geavanceerde formulieren te bouwen die gebruik maken van een achterliggende database. Praktijkvoorbeeld Tot zover de theorie. In het volgende voorbeeld wil ik laten zien op welke wijze het mogelijk is om een Word document te koppelen aan een database en vervolgens de gegevens uit deze database op te nemen in dit document. Uiteraard betreft het hier nog een redelijk eenvoudige opzet, maar het maakt duidelijk wat de kracht is van het systeem en dat dit verder kan worden uitgewerkt tot een complete bedrijfstoepassing. Allereerst maken we in Visual Studio 2008 een nieuw project aan door in het menu te kiezen voor File, New, Project. In dit geval kiezen we ervoor om het project te ontwikkelen in Visual Basic en kiezen daarom onder het kopje “Visual Basic, Office, Version2007” voor het project type “Word 2007 Document” en bevestigen twee keer via OK het aanmaken van het nieuwe project en het bijbehorende document. Het aangemaakte document verschijnt vervolgens in de Document Designer mode binnen Visual Studio als eerste geopende item. Vervolgens voegen we NORTHWIND.MDB toe aan het project, de door Microsoft ontwikkelde database die vaak als demo database wordt gebruikt, en selecteren de Employee tabel voor opname in de dataset. We doen dit om later in het project de werknemergegevens te kunnen tonen in het Word document.
46
MAGAZINE
Fig. 11: Content Controls en Data Source Na het aanmaken van een tabel voegen we door te slepen alle velden toe in een rij van een door ons vooraf aangemaakte tabel en verfraaien de tekst hier en daar om het er allemaal wat fraaier uit te laten zien. Dit gedaan hebbende is het tijd om te kijken of hetgeen we tot nu toe hebben gebouwd ook daadwerkelijk functioneert. We bouwen het
.NET VISUAL BASIC project door op F5 te drukken en vervolgens zal het document dat we net hebben gemodelleerd worden geopend in Word. Tevens zullen daarbij de gegevens van de eerste werknemer worden opgehaald en getoond op de eerste pagina van het Word document.
Globals.ThisDocument.PlainTextContentControl11. DataBindings("Text").WriteValue() Globals.ThisDocument.EmployeesBindingSource.EndEdit() Globals.ThisDocument.EmployeesTableAdapter. Update(Globals.ThisDocument.NorthwindDataSet.Employees) MsgBox("Functie bijgewerkt")
PlainTextContentControl11 in de code is de Content Control voor de achternaam van de medewerker. Als we nu opnieuw het project laten draaien en op een van de medewerkers de functie wijzigen om daarna van record te wisselen, dan zul je zien dat de gegevens daadwerkelijk worden vastgehouden.
Fig. 12: Resultaat Output Op zich nog niet zo heel spannend, maar we willen natuurlijk ook kijken of we door de data van de database heen kunnen manouvreren en zelfs de data kunnen aanpassen en opslaan in de database.
Conclusie In dit artikel is getoond wat de Word Content Controls zijn en welke events op deze controls beschikbaar zijn, en bovendien dat het mogelijk is om een koppeling te leggen naar een achterliggende database en zelfs wijzigingen vast te leggen. Met deze informatie moet men zelfstandig grotere projecten kunnen uitvoeren. Word Content Controls zijn flexibele en stabiele elementen in de Word 2007 omgeving die het mogelijk maken om documenten zeer intuïtief te laten werken en gebruikers te ondersteunen bij het aanmaken van documenten. De Content Controls waren oorspronkelijk bedoeld om de wat instabielere Bookmarks te vervangen, maar zijn vele malen krachtiger en beter inzetbaar. Door de laatste toevoegingen van VSTO in Visual Studio 2008 is het vooral eenvoudiger geworden om krachtige OBA’s (Office Business Applications) te ontwerpen en ontwikkelen. Ontsluiten van uw edrijfsinformatie naar de eindgebruiker behoort hierbij zeker tot één van de mogelijkheden om de productiviteit binnen het bedrijf te verhogen •
Maarten van Stam Allereerst voegen we daarvoor aan ons project een Ribbon toe door rechts te klikken op de projectnaam en te kiezen voor “Add, New Item”. Onder de Office sectie kiezen we voor “Ribbon (Visual Designer)”. Een nieuwe Ribbon zal nu in de designer worden geopend zodat we nu een tweetal Buttons kunnen toevoegen aan de Group die standaard al voor ons is aangemaakt. Dubbelklik nu op de eerste button en voeg de code toe om terug te gaan naar het voorgaande record in de database: Globals.ThisDocument.EmployeesBindingSource.MovePrevious()
Dubbeklik op de tweede button en voeg ook daar extra code toe, deze keer om verder te gaan naar het volgende record in de database:
Maarten van Stam is al meer dan 20 jaar werkzaam als Software Engineer. Hij is begonnen met programmeren van dBase en Clipper (DOS) systemen in de beginjaren tachtig. Pascal/C++ eind 80, C++/VB voor Windows beginjaren negentig en ontwikkelt tegenwoordig hoofdzakelijk in VB.NET/C# Gespecialiseerd in Office developmet/.NET/VSTO Awarded MVP –Visual Developer- VSTO Member Microsoft Office “14” Developer Advisory Council
Globals.ThisDocument.EmployeesBindingSource.MoveNext()
Opnieuw starten we het project op door op F5 te drukken. Als alles goed is gegaan zien we nu achteraan op de Ribbon een extra Tab verschijnen die “Add-Ins” heet met daarop de zojuist aangemaakte buttons. Druk nu een aantal keer op de knoppen. Het document zal afhankelijk van de gekozen knop de volgende of het vorige record ophalen uit de database en in het document tonen. Wat nu als we een gegeven wijzigen en daarna van de pagina af gaan. Probeer dit eens door de functie van de medewerker te selecteren en daar deze tekst te wijzigen in iets anders. Vervolgens druk je eenmaal op volgende en eenmaal op vorige record om weer terug te keren op het gewijzigde record. Je zult zien dat de wijziging **niet** is opgeslagen. We moeten iets extra’s doen om de gegevens ook daadwerkelijk vast te houden. We maken om dit mogelijk te maken een extra button aan op de Ribbon en voegen de volgende code toe:
Delphi
TIP:
Opnemen en afspelen toetsaanslagen Met de toets-combinatie <shift>R (Record) kun je een reeks toetsaanslagen (geen muiskliks !!) opnemen, die je met <shift>P (Play) weer kunt afspelen. Handig voor veel gelijksoortige operaties.
magazine voor software development 47
Advertentie Avanade
.NET ASP
Michiel van Otegem
ASP.NET onder de Motorkap: Het Provider Model Nader Bekeken Het Provider Model Design Pattern dat in ASP.NET 2.0 werd geïntroduceerd wordt door sommigen geprezen en door anderen vervloekt. Zoals zoveel dingen geldt dat het Provider Model soms heel goed voldoet en soms absoluut niet. Het is aan ons als ontwikkelaars om te bepalen wanneer we het wel of niet moeten toepassen. Daarom is het belangrijk om de voor- en nadelen goed te kennen. Het Provider Model legt een expliciete scheiding tussen de API en de implementatie daarvan, net zoals een interface dat doet. Wanneer we werken met een interface is het onze taak om objecten te gebruiken die voldoen aan dit interface. Bij de binding tussen interface en implementatie is dus (over het algemeen) werk vereist van ons als ontwikkelaars. Bij het Provider Model wordt de binding vastgesteld op basis van de configuratie, waardoor deze eenvoudig veranderd kan worden zonder tussenkomst van een ontwikkelaar. Kort door de bocht is het Provider Model een interface dat run-time ingesteld kan worden. Flexibiliteit… en meer! Als ontwikkelaar is het werken met een Provider Model API niet veel anders als het werken met een interface. Het biedt ons de flexibiliteit om een andere implementatie te kiezen (of te maken) zonder de code die het interface gebruikt te hoeven veranderen. Dit is het meest gehoorde voordeel dat de voorstanders van het Provider Model noemen. Hoewel dit zeker een voordeel is, laat de overeenkomst met een interface al zien dat er meer technieken zijn waarmee hetzelfde voordeel te halen valt. Dat de te gebruiken implementatie in de configuratie bepaald wordt heeft echter een voordeel waar wij als ontwikkelaars niet vaak bij stil staan: je hoeft niet te kunnen programmeren om te bepalen welke implementatie gebruikt wordt. Als je, met dit gegeven in je achterhoofd, verder kijkt naar hoe het Provider Model toegepast wordt in ASP.NET, zie je dat het verbeterde mogelijkheden biedt om declaratief te kunnen programmeren. Controls als Login, LoginStatus, SiteMapPath, enz. zijn allemaal te gebruiken zonder dat je (diepgaande) programmeerkennis nodig hebt. Voor iemand die alleen bekend is met HTML, zijn het een soort HTML++ elementen die naast opmaak ook functionaliteit bieden. Daarmee gaat het een stap verder in de scheiding tussen ontwerper en ontwikkelaar dan het code behind model, omdat de ontwikkelaar bij het maken van pagina’s eigenlijk helemaal niet meer nodig is. Dat wordt puur het domein van de mensen die verstand hebben van het maken van een goede gebruikersinterface. Aangezien ontwikkelaars daar meestal niet toe in staat zijn is dat eigenlijk ook maar beter zo.
Het Provider Model biedt verbeterde mogelijkheden om declaratief te kunnen programmeren Beperkt toepasbaar Applicaties maken zonder te hoeven “programmeren” is de heilige graal die we al decennia lang met 4de generatie talen (4GL) proberen te vinden. We zijn er ondertussen achter dat we een eind kunnen
komen, zolang we het domein enorm beperken. Diezelfde beperking geldt in feite ook voor het Provider Model. Het is vrijwel onmogelijk om een API en een verzameling controls te ontwikkelen die in alle situaties de oplossing kunnen bieden. Hoe handig het ASP.NET Membership systeem ook is voor een groot gedeelte van de websites, er zijn altijd applicaties waarin het Membership systeem behoorlijk tekort schiet.
Het Provider Model biedt geen specialistische oplossingen, het gaat uit van de grootste gemene deler Dat komt omdat het Provider Model niet kan voorzien in specialistische oplossingen, het gaat juist uit van de grootste gemene deler. De onderdelen in ASP.NET die werken op basis van het Provider Model bevatten alleen de meest voorkomende functionaliteit. Treed je buiten die paden, dan zul je uiteindelijk toch zelf iets moeten bouwen. De kunst is om dat zoveel mogelijk te doen met “aanbouw”, zodat je niet alles over boord hoeft te zetten dat ASP.NET biedt. Zelf implementeren Als je je zo min mogelijk wilt bemoeien met het gebruikersinterface, dan kan het handig zijn om functionaliteit volgens het Provider Model op te zetten. Als je eenmaal weet hoe dit moet, is de extra moeite die je daarvoor moet doen verwaarloosbaar. Op de MSDN website is een uitstekende uitleg te vinden van Rob Howard over hoe het Provider Model werkt en hoe je het implementeert. Deel 1 kun je vinden op http://msdn.microsoft.com/en-us/library/ms972319.aspx en deel 2 op http://msdn.microsoft.com/en-us/library/ms972370.aspx. Houd er rekening mee dat de uitleg dateert van voor de release van ASP.NET 2.0 en dat classes als ProviderBase inmiddels in het .NET Framework beschikbaar zijn. Michiel van Otegem [email protected] •
magazine voor software development 49
INFORMATION
WORKER
Marianne van Wanrooij
NAV+VSTO+WPF+LINQ
= OBA Een simpel sommetje voor een geavanceerde oplossing . . . Er zijn meer dan 500 miljoen Office gebruikers. Dat maakt Office (Word, Excel, PowerPoint, etc.) tot een van de meeste gebruikte client applicaties die Microsoft te bieden heeft. Daarom is een Office Business Application (OBA) een logische oplossing om business data in de context van de “everyday-work-environment” te brengen. Een OBA bestaat uit vier componenten; de LOB applicatie (bijv. SAP, PeopleSoft, of, zoals in dit artikel, MS Dynamics NAV), een aangepaste Office client applicatie (hier gebruik ik MS Word 2007), Microsoft Office SharePoint Server 2007 (MOSS2007) en de services die deze drie componenten aan elkaar knopen tot een bruikbare oplossing. Microsoft Dynamics NAV Navision (NAV) is een business management solution voor het MKB. Met functionaliteit voor de financiële verslaglegging, voorraad beheer, service management, human resource management, etc. is het een volledig ERP systeem dat de “standaard” bedrijfsprocessen ondersteunt. Vele ISV’s (Independent Software Vendors) bieden diverse add-in’s op NAV die branchspecifieke bedrijfsprocessen ondersteunen. NAV kent een eigen ontwikkelomgeving, C/SIDE (client/server integrated development environment). Het NAV team van Microsoft heeft hiervoor een eigen programmeertaal ontwikkeld. De NAV programmeertaal voor het schrijven van “eigen” business rules heet C/AL (client/application language). Hoewel deze taal geschreven is in C++, vind je daarvan maar weinig terug. NAV heeft standaard een integratie met Office Word en Excel 2003 op basis van WordML. Wanneer een gebruiker een Word document wil genereren met informatie uit NAV, zal hij van te voren moeten weten welke informatie hij wil gebruiken, en de daarbij behorende template in NAV moeten kiezen of maken. Om nieuwe templates aan te kunnen bieden zal er een ontwikkelaar met gedegen XML kennis aan te pas moeten komen.
Office Business Applications (OBA’s) zijn oplossingen die bestaan uit services, tools en servers die aangeboden worden in het Microsoft 2007 Office system en kenmerken zich door de integratie met line of business (LOB) applicaties 50
MAGAZINE
Fig. 1: Microsoft Dynamics NAV Waarschijnlijk komt eind dit jaar de nieuwe versie van MS Dynamics NAV uit (NAV 2009). Deze zal een 3-tier architectuur hebben met een webservice laag welke je kan gebruiken om Add-On’s op Navision te schrijven in .NET. Dit geeft ook veel mogelijkheden om OBA’s te schrijven voor NAV. Het weerhoudt ons echter niet om alvast te experimenteren met OBA voor NAV. Het probleem Stel dat een account manager een brief wil schrijven naar een van zijn klanten. Als hij een gebruiker van NAV is, kan hij kiezen voor een standaard brief-template uit NAV. De naam en adresgegevens van de
INFORMATION WORKER klant staat netjes in de gegenereerde brief. De account manager begint met het schrijven van zijn brief en bedenkt dat hij een nieuw product onder de aandacht van zijn klant wil brengen. Wil hij de juiste informatie in het document hebben, zal hij NAV moeten openen, het product op moeten zoeken en de informatie moeten knippen en plakken. Maar voor hetzelfde geld is de accountmanager geen gebruiker van NAV, en ook dan wil hij gebruik maken van de informatie die in NAV staat, zoals klantgegevens en productinformatie. In dit artikel laat ik zien hoe je deze productinformatie in een document kunt plaatsen zonder dat je als gebruiker Word hoeft te verlaten. Hiervoor wordt een zoekfunctie gemaakt in de taskpane van Word met behulp van VSTO. In dit voorbeeld maken we er een document-level add-in van. Hierdoor is de functionaliteit alleen beschikbaar indien er voor deze template gekozen wordt. Je kunt er ook een application add-in van maken; dan is de functionaliteit beschikbaar wanneer je Word opstart. Vanuit een development point of view zitten daar ook verschillen in. Deze verschillen zullen ook de revue passeren. De zoekfunctie zal met behulp van LINQ to SQL een resultaat geven. Om het zoekresultaat er aantrekkelijk uit te laten zien laten we de foto’s van de producten zien waarbij de naam en de prijs van het product wordt getoond. Door gebruik te maken van een fish-eye worden foto’s vergroot bij een mouse-over. Dit is mogelijk door gebruik temaken van Windows Presentation Foundation (WPF).
VSTO Word 2007 document We beginnen met het creëren van een Word-template Dit hebben we vast allemaal al eens gedaan, dus dat heeft geen uitleg nodig! Open Word en creëer een document met de juiste opmaak. In deze OBA wordt via het double-click event op een item de geselecteerde productinformatie op de plek waar de cursor staat geplaatst. Het is ook mogelijk om de informatie op een bepaalde, vaste plek in het document te plaatsen door gebruik te maken van Content Controls. Content Controls hebben het voordeel dat er logica voor kan worden geschreven, zoals het plaatsen van een lock zodat de content read-only wordt.
TIP: Gebruik Content Controls indien je de data op vaste plekken in het document wilt plaatsen en logica wilt schrijven op dat stuk data. De Content Controls vind je op de Developer Tab in de Ribbon. Mocht deze niet zichtbaar zijn klik dan op de Office Button (links bovenin), kies voor Word Options, en vink de “Show Developers tab in Ribbon” aan.
TIP: Content Controls kun je ook gebruiken om MOSS 2007 document library columns (metadata) te plaatsen en aan te passen binnen het document.
Nu begint het leuke werk! Om maatwerk op document niveau te schrijven maak je een Word 2007 document project aan in Visual studio. Klik op File – New – Project; in het Project scherm kies je voor Visual C# (of VB.NET) Office – 2007 – Word 2007 document. Daarna krijg je een scherm waarin je een keuze krijgt om een nieuw document te creëren of een bestaand document te gebruiken. Omdat we de opmaak al in een document hebben gezet, kiezen we voor het demo document.
Fig. 2: Het eindresultaat: OBA in action Hoe krijg je dit voor elkaar? In figuur 3 wordt de architectuur getoond van de document-level customization die in dit artikel verder wordt beschreven.
Wanneer het project is geladen zie je binnen de IDE van Visual Studio het document in een Word omgeving. Hierdoor werken Word en het document als een designer binnen Visual Studio.
Fig. 3: Architectuur van document-level customization met WPF en LINQ
magazine voor software development 51
INFORMATION WORKER
TIP: In het artikel “Synchroniseren van de Ribbon met een TaskPane in Word 2007” van Maurice de Beijer wordt uitgelegd hoe je de interactie tussen de TaskPane en de Ribbon goed kunt laten werken (http://www.sdn.nl/Default.aspx?tabid=50&itemid=2525).
TaskPane vs ActionsPane We beginnen met het toevoegen van een Windows.Forms.UserControl. Deze UserControl (met ID UserControl1) zal op de TaskPane geplaatst worden. Afhankelijk of het een document-level customization is of een application-level add-in is, creëer je resp. een ActionsPane of een TaskPane. Een ActionsPane is specifiek voor een document en wordt “gehost” binnen de Office task pane samen met andere ActionsPanes zoals de wel bekende “Styles and Formatting”-action pane. ActionsPanes kun je dan ook alleen in document-level applicatie aanmaken. Voor een Word AddIn (application-level) is de code als volgt: Private void ThisAddIn_Startup( object sender, System.EventArgs e)
geavanceerde wijze om informatie te tonen. Op het internet kun je veel WPF controls vinden. De fish-eye die ik hier gebruik is gedownload van Code Project (http://www.codeproject.com/KB/WPF/). In dit voorbeeld zijn twee WPF UserControls gebruikt, een voor de functionaliteit (zoek textbox en button) en een voor het resultaat. Een WPF UserControl bestaat uit een CodeBehind-file en een XAMLfile. Het is aan te bevelen om Expression Blend te gebruiken voor het design van een WPF UserControl. In Expression Blend kun je het VS project laden en je controls opmaken. Op de achtergrond maakt hij de XAML-code voor je aan. In de WPF UserControl, UserControl2, staat in het Grid een StackPanel met een tekstbox en een button om de zoekfunctie te starten, en een ListBox die op basis van de FishEye Panel de buttons, zoals ze in UserControl3, worden opgesteld toont. De FishEye is een class die overerft van de base class Panel. De ListBox gebruikt als ItemsPanelTemplate de FishEyePanel.
{ Microsoft.Office.Tools.CustomTaskPane myTaskPane = CustomTaskPanes.Add( new UserControl1(), “Search products”);
...
// set taskpane vertical at the bottom myTaskPane.DockPosition =
Listing 3: FishEye panel als template voor de listbox panel
MsoDockPosition.msoCTPDockPositionBottom; // don’t forget to set visibility, otherwise
De XAML van het grid ziet er als volgt uit:
// you will not see it myTaskPane.Visible = true; }
...
Listing 1: TaskPane in Application Level AddIn
Voor een document level customization ziet de code er als volgt uit:
Relief="0.39" Smoothness="0.54" LightAngle="134"/>
Private void ThisDocument_Startup( object sender, System.EventArgs e)
{ this.ActionsPane.Controls.Add(new UserControl1());
StartPoint="0.024,0.005">
// set taskpane vertical at the bottom this.CommandBars[“Task Pane”].Position = Microsoft.Office.Core.MsoBarPosition.msoBarBottom;
<StackPanel Orientation="Vertical">
}
Listing 2: ActionsPane in Document-Level customization
BorderBrush="#FF26394A" Background="#5FFFFFFF"/> <Button x:Name="SearchButton"
Een gebruiker kan een TaskPane afsluiten. Daarom zul je functionaliteit moeten schrijven die het mogelijk maakt om de TaskPane weer te tonen. Deze functionaliteit kun je het beste in de Ribbon plaatsen, maar het synchroon houden van de Ribbon met de TaskPane is geen eenvoudige opgave.
Content="Search for Items" Margin="5,0,5,5">
Windows Presentation Foundation Het gebruik van WPF geeft een verbeterde user interface en biedt een
<Button.Background>
52
MAGAZINE
INFORMATION WORKER
LINQ en LambdaExpressions De data komt uit NAV. In deze demo is gekozen om via LINQ to SQL een koppeling te maken met de NAV demo database Contoso. Het is niet aan te raden om direct de NAV database te benaderen. Hierdoor verlies je de business rules die wellicht op de data zitten. Voor deze demo is wel een directe koppeling gemaakt. Hiermee wil ik alleen aantonen hoe eenvoudig het gebruik van LINQ to SQL is. De informatie die we willen gebruiken staat in de NAV Items tabel. Voeg een LINQ to SQL class toe aan het project (“DataContoso”). Daarna maak je een dataconnectie met de NAV demo database en sleep je de Contoso$Item tabel op de design view van het DBML-file. Na een build worden een layout en designer.cs file aangepast, en wordt van de tabellen en relaties een object model opgebouwd. De databinding in de WPF UserControl XAML is op basis van een LINQ query die in UserControl2 wordt opgebouwd.
private void LoadContosoItems(string SearchText) {
AnimationMilliseconds="150" ScaleToFit="true"/>
var DB = new DataContorsoDataContext();
var foundItems =
from item in DB.Contoso_Items
where (item.Description.Contains(SearchText))
select item;
Items.ItemsSource = foundItems; }
Listing 6: Lambda Expression op de DataContoso LINQ to SQL class
...
Listing 4: XAML van WPF UserControl UserControl2 In de WPF Usercontrol UserControl3 worden de image buttons geplaats waar een overlay van de tekst wordt getoond. De plaatjes staan op een fileshare en hebben dezelfde naam als de naam van het produkt in de NAV database.
In de WPF UserControl XAML zie je ook twee StaticResources staan als Converter van de data. Voor beide Converters bestaan een dataconversie die tijdens de databinding aangeroepen wordt en een class, met een IValueConverter interface, die een object teruggeeft. Hierdoor kun je de data uit de database manipuleren. class DoubleToCurrency : IValueConverter { public object Convert(object value, Type targetType,
...
object parameter,
System.Globalization.CultureInfo culture)
BorderBrush="#4C000000" BorderThickness="1,1,1,1" {
Width="100" Height="50">
return String.Format("{0:C}", value);
}
object parameter,
System.Globalization.CultureInfo culture)
{
<StackPanel>
throw new NotImplementedException();
Text="{Binding Path=Description}" Margin="5,5,5,5">
}
Listing 7: Converter voor de tariefinformatie
Nu hebben we de functionaliteit voor de zoekfunctie en de databinding van het resultaat. De volgende stap is om op een double-click op een geselecteerd item de informatie van het product in het Word-document te plaatsen. Hiervoor maken we eerst een functie die controleert of het Item is geselecteerd en vervolgens geeft het de betreffende informatie door naar de functie die de tekst plaatst op de plek waar de cursor staat.
private void InsertSelectedItemInWord() {
if (Items.SelectedItem != null)
...
{
Listing 5: XAML Border van WPF UserControl UserControl3 magazine voor software development 53
INFORMATION WORKER var item = (Contoso_Item)Items.SelectedItem; InsertTextAtSelection ( String.Format("Product: {0} \r\n Price:{1}", item.Description, item.Unit_Price) ); } }
Meer lezen • Microsoft Office Business Applications for Office SharePoint Server: www.microsoft.com/MSpress/books/9471.aspx • Ribbon en TaskPane synchronisatie - Maurice de Beijer: http://www.sdn.nl/Default.aspx?tabid=50&itemid=2525 • Deploying a Visual Studio Tools for the Office System 3.0 Solution for the 2007 Microsoft Office System Using Windows Installer • deel 1: http://msdn.microsoft.com/en-us/library/cc563937.aspx • deel 2: http://msdn.microsoft.com/en-us/library/cc616991.aspx
Voor een document-level customization kan dit met de volgende code:
Marianne van Wanrooij
private void InsertTextAtSelection(string text) { Word.Selection currentSelection = Globals.ThisDocument.ThisApplication.Selection; currentSelection.InsertAfter(text); }
Let op: Het kan zijn dat de gebruiker de “Insert” functie aan heeft staan. Hierdoor zou je tekst kunnen overschrijven, wat niet de bedoeling is. Door de Application.Options.Overtype op false te zetten wordt de tekst toegevoegd. Het is wel raadzaam om OverType daarna weer terug te zetten zoals de gebruiker dit had ingesteld. Nu het laatste stukje … de functies moeten aangeroepen worden op het event. Wanneer op de search button wordt geklikt, dient de zoekfunctie LoadContosoItems aangeroepen te worden. En op het doubleclick event wordt de functie InsertSelectedItemInWord aangeroepen, zodat de informatie over het product toegevoegd wordt aan het document. public UserControl2() {
Marianne van Wanrooij is de track owner van de Information Worker sectie binnen SDN en is organisator van de Dutch Information Worker User Group (DIWUG). Regelmatig spreekt zij voor verschillende communities in binnen- en buitenland en ze houdt een IW website www.information-worker.org bij. Marianne is een solution architect bij Connected Solutions (www.connected-solutions.net), waar ze zich bezig houdt met het vertalen van bedrijfprocessen naar IW oplossingen.
C#
TIP:
InitializeComponent(); SearchButton.Click += (sender,e) =>
Microsoft Source Analysis voor C#
{ LoadContosoItems(SearchBox.Text); }; Items.MouseDoubleClick += (sender,e) => { InsertSelectedItemInWord(); }; }
Tenslotte … Je zou dit nog verder uit kunnen breiden met een koppeling met een MOSS document library, maar dat is voor een volgend artikel. Ook heb ik niet stilgestaan bij de deployment van een VSTO-applicatie. Hiervoor verwijs ik je graag naar de Office Developer Center waar twee artikelen staan over de deployment van VSTO-applicaties met Windows Installer. In dit artikel wilde ik laten zien dat je met VSTO, WPF en LINQ op basis van informatie uit Navision een eenvoudige maar zeer bruikbare OBA kan maken. De complete code is te downloaden vanaf de SDN site. Met dank aan Robertjan Tuit, die me geholpen heeft met het WPF gedeelte.
54
MAGAZINE
Altijd al willen weten of je code goed in elkaar zit? Dan krijg je nu daarvoor de gereedschappen tot je beschikking, want daarvoor is Microsoft Source Analysis for C# gereleased. Je kunt deze downloaden op: http://code.msdn.microsoft.com/sourceanalysis. In tegenstelling tot FxCop die de gecompileerde binaries analyseert, analyseert Source Analysis rechtstreeks de source code. Intern bij Microsoft wordt deze tool onder de naam StyleCop ook gebruikt om te zorgen dat de layout, leesbaarheid, onderhoudbaarheid en documentatie voldoet aan de ‘best practices’. Downloaden en gebruiken dus!
ARCHITECTURE
Roy Cornelissen
Data Centric Services met ADO.NET Data Services De laatste tijd is de manier waarop we web applicaties bouwen sterk aan het veranderen. Traditioneel werden complete pagina's door server controls gerenderd en heen en weer gestuurd tussen server en client. Een dergelijke pagina bevatte structuur, opmaak en data. Tegenwoordig zien we steeds meer AJAX enabled websites, waarbij delen van de pagina worden ververst op basis van gebruikersacties. De browser wordt meer aan het werk gezet met Javascript en Dynamic HTML, of zelfs met gecompileerde user interfaces in Silverlight of Flash. De grootste driver hiervoor is de behoefte aan rijkere en meer geavanceerde gebruikersinteractie. Dit vergt een andere manier van werken en heeft geleid tot een nieuw type applicatie: de Rich Internet Application (RIA). Een belangrijk element bij het ontwikkelen van RIA’s is het uitwisselen van gegevens via het web. Met de komst van RIA’s is de manier waarop deze data wordt geconsumeerd en gebruikt, veranderd. We hebben te maken met verschillende browsers, Javascript en web standaarden als Atom/RSS die een vlucht hebben genomen. Er is behoefte aan het kunnen scheiden van data, structuur en opmaak. Een geëigende methode om dat te realiseren is het bouwen van XML web services. Microsoft heeft de soorten services die zoal worden gebouwd, nog eens onder de loep genomen en heeft geconstateerd dat er eigenlijk behoefte is aan twee soorten interfaces: operation centric en data centric.
Er is behoefte aan 2 soorten interfaces: Operation Centric en Data Centric Bij operation centric services draait het om operaties en de semantiek van operaties onderling. Voorbeelden hiervan zijn: het boeken van een vakantie, waarbij je een verzoek doet en verwacht dat er een aantal reserveringen wordt gemaakt, of een login mechanisme, waarbij je credentials opstuurt en verwacht dat ze worden gevalideerd en je een soort token terugkrijgt waarmee je verder kunt in de applicatie. Deze operation centric services zijn we al tamelijk gewoon te bouwen
binnen onze service georiënteerde architecturen met standaarden als SOAP/XML en frameworks als WCF. Door Microsoft worden hiervoor de termen Activity Services of Process Services gebruikt. In data centric services draait het om het adresseren, opvragen en manipuleren van gegevens. De operaties op die gegevens zijn veelal uniform (creëren, opvragen, muteren en verwijderen - CRUD) en datgene wat over de lijn wordt gestuurd, is alleen de data. Microsoft noemt dit type services ook wel Entity Services. Vooral data centric services zijn gewild in RIA’s omdat er behoefte is aan een standaard manier om gegevens helemaal tot in de presentatielaag (de browser) te krijgen, gescheiden van structuur en opmaak. De trend is daarom het gebruik van zogenaamde RESTful services. REST is een acroniem voor Representational State Transfer en staat voor een manier van denken waarbij gegevens (resources) in een bepaalde staat worden gecommuniceerd en kunnen worden aangewezen met unieke adressen: URI’s. REST is geïntroduceerd in het proefschrift van Roy Fielding, een van de belangrijkste auteurs van het HTTP protocol. HTTP is zelf een goed voorbeeld van een implementatie van REST principes: resources (HTML documenten, plaatjes, Flash bestanden) worden aangeduid met URL´s. Ze worden gemanipuleerd met simpele opdrachten (HTTP verbs) als PUT (C), GET (R), POST (U) en DELETE (D). Naast de behoefte aan dit type interfaces raakt ook het formaat waarin gegevens worden gecommuniceerd steeds meer gestandaardiseerd: Atom en RSS zijn veel gebruikte formaten in de wereld van blogs en mash-ups, JSON (JavaScript Object Notation) is bij uitstek geschikt voor AJAX clients. Praktijkvoorbeelden van RESTful, data centric services zijn Google Base en de Windows Live services zoals Live Photos. ADO.NET Data Services Het zelf bouwen van services met data centric interfaces en op basis van eerdergenoemde standaarden is goed mogelijk, maar is veel werk. Als je voor alle entiteiten in je gegevensdomein lees- en schrijfoperaties moet bouwen in een traditionele service, dan kan dat veel methods vergen en dus veel werk opleveren. Als de interface van data centric services uniform is en de formaten voor communicatie ook gedefinieerd zijn, dan moet het mogelijk zijn een framework te ontwikkelen dat het bouwen van zulke services gemakkelijk maakt. Daarnaast zou er een uniform programmeermodel moeten worden bedacht voor clients die deze services consumeren. Microsoft heeft deze handschoen opgepakt, wat heeft geresulteerd in ADO.NET Data Services. Dit project was eerder bekend onder de codenaam "Astoria". ADO.NET Data Services is een framework om gegevens te ontsluiten op een uniforme manier. Hierbij heeft Microsoft sterk gekeken naar de geldende standaarden op het web om niet zelf opnieuw het wiel uit te hoeven vinden. ADO.NET Data Services leunt daarom sterk op HTTP voor adressering en transport en Atom/Atom Publishing Protocol (APP) of JSON als gegevensformaat. Door hiervoor te kiezen krijg je
magazine voor software development 55
ARCHITECTURE zaken als HTTP caching, proxies, security mechanismes, etc. cadeau. Daarnaast zijn de uitwisselingsformaten breed toepasbaar, wat de inzetbaarheid op verschillende platforms vergroot. Verderop in dit artikel zullen we zien dat ADO.NET Data Services leunt op nog meer “standaard” zaken die reeds voorhanden zijn. ADO.NET Data Services is namelijk gebouwd bovenop WCF, ASP.NET en LINQ technologie.
Astoria is een framework om gegevens te ontsluiten op een uniforme manier Van SP1 voor .NET framework 3.5 is begin mei een beta-versie uitgebracht. In deze uitbreiding zitten zaken als ADO Entity Framework, ASP.NET extensions en ook ADO.NET Data Services. Tegelijkertijd met deze framework-update is ook een beta-versie van SP1 voor Visual Studio 2008 beschikbaar gekomen, waarin de tools en designers voor deze nieuwe zaken zitten.
Na het installeren van SP1 is een nieuw item type beschikbaar gekomen in Visual Studio: de ADO.NET Data Service. Wanneer je een nieuw item van dit type creëert, krijg je een .SVC bestand met codebehind-file. Als je deze codebehind bekijkt, dan zul je zien dat de implementatieclass is afgeleid van de generic base class Data Service. Het type T geeft aan wat het datatype is van je gegevensbron. Dit is een class met properties van het type IQueryable en IUpdateable, bijvoorbeeld een Entity Framework model. Standaard zal de gegenereerde service nog geen data ontsluiten. Dit kun je regelen met policies, waarop we verderop in dit artikel terugkomen. Voor demonstratiedoeleinden roepen we config.SetEntitySetAccessRule("*", EntitySetRights.All) aan, om aan te geven dat alle entiteiten in ons model via de service opvraagbaar en muteerbaar zijn. Dit wordt sterk afgeraden voor productiecode. In listing 1 is een voorbeeld te zien van een Data Service voor de AdventureWorks database. using System; using System.Collections.Generic; using System.Data.Services; using System.Linq;
Een interessant aspect aan de ontwikkeling van deze technologie is dat het Astoria team al vanaf het eerste prototype contact heeft gezocht met de .NET community. Op deze manier ontvingen ze al in een vroeg stadium feedback over de haalbaarheid en het nut van het project. Door een zeer open ontwerpproces - iedere belangrijke beslissing en overweging werd gedeeld op het team blog - kon de community invloed uitoefenen op het eindresultaat.
using System.ServiceModel.Web; using System.Web; using AdventureWorksModel; using System.Linq.Expressions; namespace AWWeb { // Data service gebaseerd op AWEntities bron
Aan de slag Een ADO.NET Data Service bestaat uit twee delen: een vast deel voor het afhandelen van requests en serialisatie en een pluggable deel, de data source. In de meeste gevallen zal data zich in een database bevinden, en in een Microsoft only wereld zal dat SQL Server zijn. De wereld is echter niet altijd zo eenduidig, dus het kan voorkomen dat gegevens in andere bronnen staan dan relationele databases, bijv. flat files, XML of in memory. Voor het omgaan met gegevens, onafhankelijk van de bron, is in .NET 3.5 al een erg krachtige feature beschikbaar gekomen onder de naam LINQ (Language Integrated Query). Doordat een provider model wordt gebruikt, kunnen uiteenlopende bronnen worden benaderd via LINQ expressies. ADO.NET Data Services maakt ook gebruik van dit principe, door te leunen op LINQ en in het bijzonder de IQueryable interface voor het beschikbaar stellen van entiteiten uit de gegevensbron. Omdat IQueryable gericht is op het lezen van gegevens, is daaraan IUpdatable toegevoegd voor het manipuleren van de gegevens. Zolang een provider deze twee interfaces ondersteunt, maakt het niet uit waar je data zich bevindt, om er een ADO.NET Data Service van te maken.
Een ADO.NET Data Service bestaat uit twee delen: een voor het afhandelen van requests en serialisatie en een voor de data source Voor gegevens in een relationele database is het ADO.NET Entity Framework bij uitstek geschikt. Met dit framework kun je snel en gemakkelijk de entiteiten in je gegevensmodel definiëren als .NET classes en daarbij een mapping aangeven op het onderliggende datamodel. Entity framework biedt op zijn beurt weer ondersteuning voor LINQ to SQL, waarbij LINQ queries binnen de .NET omgeving worden vertaald naar SQL queries op de database. Ook stored procedures worden ondersteund, voor het geval je geen prijs stelt op dynamische SQL queries vanuit je code. Wanneer je een Entity Framework model aanmaakt voor je database, kun je deze rechtstreeks gebruiken als bron voor een ADO.NET Data Service.
56
MAGAZINE
public class AWData : DataService { public static void InitializeService (IDataServiceConfiguration config) { // Toegang tot alle entity sets toegestaan config.SetEntitySetAccessRule ("*", EntitySetRights.All); } } }
Listing 1: Data service voorbeeld Queries stellen Als we de service aan ons model hebben gekoppeld en de access rules hebben gezet, kunnen we de service meteen testen door queries uit te voeren. Omdat alle data via URI´s beschikbaar is en HTTP het protocol is, kunnen we de browser als client gebruiken. Wanneer we de service starten en met Internet Explorer er naartoe surfen, dan zien we allereerst een overzicht van alle toplevel-entiteiten die de service beschikbaar stelt, in XML formaat. Door de data browsen is vervolgens heel simpel: dit doe je door de naam van zo'n entiteit toe te voegen aan de URI in de adresbalk. Het basisformaat van een URI voor data services is: http://host/vdir/<service>/<EntitySet> [() [/ [()/...] ] ]
In de AdventureWorks data service van listing 1 geeft http://server/AdventureWorksData.svc/Product ons alle entries in de Product tabel, http://server/AdventureWorksData.svc/Product(3) geeft ons het product met primary key 3. De naam "Product" in de URI is onze EntitySet en "3" is de Key.
ARCHITECTURE Verder kun je de gehele hiërarchie van de database doorwandelen: http://server/AdventureWorksData.svc/Product(3)/WorkOrder geeft bijvoorbeeld alle work orders van product 3. "WorkOrder" is in dit voorbeeld een NavigationProperty. Navigation properties kun je zo diep nesten als de structuur van je model toelaat. Wanneer je een lijst van items opvraagt, zal je opvallen dat Internet Explorer de lijst presenteert als RSS feed, met de welbekende style sheet en de link "subscribe to this feed". Dat komt doordat het standaard formaat van een data service Atom/APP is. Een enkel record in de tabel wordt door de service dan ook teruggegeven als een <entry> element zoals je die kent in Atom. Het formaat dat de data service teruggeeft kun je beïnvloeden door de Accept HTTP header in het request. Door "application/json" mee te geven krijg je de data in JSON formaat. Door gebruik te maken van het AJAX framework zal alle data die je vanuit Javascript opvraagt, in JSON formaat door de server worden geretourneerd.
Een lijst van items wordt gepresenteerd als RSS feed Standaard zal de service een platte collectie van entiteiten teruggeven, gesorteerd op de primary key in de tabel. Microsoft heeft echter goed gekeken naar de bewerkingen die in veel applicaties nog op data sets worden gedaan voordat ze worden gepresenteerd. Denk hierbij aan sorteren, paging en het tonen van bepaalde detail-informatie in de objectgraaf. Hiervoor is een aantal argumenten beschikbaar, die je in de querystring van de URI kunt opgeven. Dit zijn: • $expand: gebruik je om gerelateerde objecten ook in de resultaten terug te krijgen, die je anders in twee losse requests zou moeten ophalen, b.v. als je de ProductVendor van de producten ook meteen wilt hebben. De ProductVendor wordt dan een property van de Product objecten in de collectie. • $orderby: beïnvloedt de sortering van de resultaten. Je kunt sorteren op properties van de objecten in de lijst. Meerdere velden worden gescheiden door komma's. Je kunt de sortering beïnvloeden met de asc (default) en desc opties. • $skip: laat de service het opgegeven aantal rijen overslaan in de resultaatlijst. Dit is vooral handig voor paging, in combinatie met de $top optie. • $top: geeft aan hoeveel rijen er moeten worden teruggegeven. Deze parameter werkt sterk samen met $orderby. Wanneer geen $orderby wordt opgegeven, wordt de primary key van de tabel gebruikt voor de sortering en wordt vervolgens $top toegepast. • $filter: fungeert als de where clause in je query: hiermee geef je expressies op om de resultaatlijst te filteren op eigenschappen. De $filter property kent weer een eigen syntax, met operators en functies voor string-, datum-, mathematische en typebewerkingen, vergelijkbaar met wat je in de common language runtime ook vindt. Het gaat te ver voor dit artikel om de hele lijst te noemen, maar ze zijn te vinden in de documentatie. De mogelijkheden die je in de querystring hebt, staan los van het formaat waarin de data wordt geretourneerd. Deze opties zijn dus altijd voorhanden. Het ADO.NET Data Services framework verzorgt de afhandeling van deze argumenten, dus zijn ze toepasbaar op iedere data service die je maakt, ongeacht de gegevensbron. Het framework vertaalt de URI naar een Linq expression tree en laat deze vervolgens los op de data. Dit brengt veel flexibiliteit met zich mee, omdat je de client kunt laten bepalen hoeveel gegevens moeten worden geretourneerd, terwijl je hier geen extra logica voor hoeft te bouwen in de service om dit allemaal te ondersteunen. Daarnaast wordt het netwerkverkeer beperkt, doordat de service deze faciliteiten standaard biedt.
Voorbeelden: • Alle producten, met daarbij de ProductVendor relatie en de bijhorende Vendor entiteit ook meegenomen: http://server/AWData.svc/Product?$expand=ProductVendor/Vendor • Uit alle producten, aflopend gesorteerd op naam, rij 30 t/m 40 (de vierde page): http://server/AWData.svc/Product?$skip=29&$top= 10&$orderby=Name desc. Volgens de notatieregels voor URI’s moet de spatie in bovenstaand voorbeeld worden vertaald naar “%20”. • Alle producten waarvan de naam (in lower case) de string ‘nuts’ bevat: http://server/AWData.svc/Product?$filter=contains (tolower(Name) ,'nuts') Client programmeermodel Naast een framework en tools om snel een data service te bouwen biedt SP1 voor Visual Studio 2008 ook een aantal libraries om clientcode voor data services mee te bouwen. ADO.NET Data Services is in eerste instantie ontwikkeld met het web in het achterhoofd, dus de ondersteuning voor clients is ook voornamelijk daarop gericht. Daarnaast integreren deze bouwblokken weer naadloos met bestaande technologie: het .NET framework en ASP.NET. Er zijn vier mogelijkheden: • Een Javascript library die in combinatie met de ASP.NET AJAX Extensions een Javascript programmeermodel biedt voor het consumeren van data services vanuit een browser; • De DataServiceDataSource control, die als data source kan dienen voor data aware ASP.NET controls, zoals de GridView. Hiermee kun je dus standaard ASP.NET controls koppelen aan iedere willekeurige ASP.NET Data Service; • De DataServiceContext en DataServiceQuery objecten in de System.Data.Services.Client library, waarmee je vanuit iedere .NET applicatie een data service kunt consumeren, en dus ook vanuit Silverlight user interfaces; • Een LINQ to Data Services provider, waarmee je in de client LINQ queries kunt bouwen om data uit data services op te vragen. Voor de laatste twee opties is ook een tool beschikbaar waarmee je op basis van de service catalog classes kunt genereren die het eniteitenmodel in de client implementeren. Deze tool is vergelijkbaar met SvcUtil.exe voor WCF services en heet dan ook toepasselijk DataSvcUtil.exe. De gegenereerde code is LINQ enabled. In figuur 1 is te zien hoe een dergelijke LINQ query runtime wordt vertaald naar een ADO.NET Data Service URI.
Fig. 1: Vertaling LINQ query naar ADO.NET Data Services URI Beveiliging en extra controle Tot zover hebben we het gehad over het beschikbaar stellen van data via een uniforme interface. Met een paar clicks en een regel code kun je een hele database aan het web hangen en voor iedereen beschikbaar stellen. De vraag rijst dan hoe we dit goed kunnen beveiligen of de gegevensset kunnen beïnvloeden op basis van een gebruikerscontext. Voor het beveiligen van services maakt ADO.NET Data Services gebruik van bestaande authenticatie-mechanismen: IIS biedt
magazine voor software development 57
ARCHITECTURE ingebouwde Windows of Basic authentication, ASP.NET biedt Forms authentication en het Membership provider model en WCF bieden behaviors om services te beveiligen. HTTPS kan worden ingezet om verkeer te versleutelen. In de data service zelf kun je met policies vrij precies bepalen welke delen van je entiteitenmodel beschikbaar zijn via de data service en welke operaties erop geoorloofd zijn. Dit doe je in de IntializeService method, die wordt gegenereerd, zodra je een ADO.NET Data Service aan je project toevoegt. Aangeven welke onderdelen op welke manier toegankelijk zijn doe je met de SetEntitySetAccessRule method van het config object dat je als parameter meekrijgt. Per onderdeel kan de toegestane wijze van toegang worden aangegeven: alles, read single, read multiple, write single, enz. Zo kun je delen van je model toegankelijk maken voor lezen en schrijven, terwijl andere delen slechts alleen lezen zijn, of kun je ervoor zorgen dat niet een complete set, maar alleen enkele items kunnen worden opgevraagd. De tweede parameter van SetEntitySetAccessRule is een enum van het type EntitySetRights, waarmee je de rechten kunt specificeren. Zie listing 2 voor een voorbeeld. // Alles toegestaan op Product config.SetEntitySetAccessRule("Product", EntitySetRights.All); // ProductReviews mogen alleen worden toegevoegd config.SetEntitySetAccessRule("ProductReview", EntitySetRights.WriteAppend); // Uit WorkOrderDetail mogen alle records worden gelezen config.SetEntitySetAccessRule("WorkOrderDetail", EntitySetRights.AllRead);
Listing 2: Entity set policies Een derde mogelijkheid om controle uit te oefenen op de gegevens die worden gevraagd of bewerkt is de QueryInterceptor. Dit is een public method die wordt gedecoreerd met het QueryInterceptor attribuut en een return type Expression> heeft. In de constructor van het QueryInterceptor attribuut geef je de naam van een entity set op. Wanneer die entity set wordt geraakt, voor lezen én schrijven, dan zal het ADO.NET Data Services framework deze method altijd uitvoeren. In zo'n query interceptor kun je business logica toevoegen en een expressie opstellen die door de data service wordt toegevoegd aan de LINQ expression tree die wordt losgelaten op de data source. In de query interceptor method kun je van alles doen, van controle op gebruikersrollen tot het aanroepen van services.
Query Interceptor maakt controle mogelijk
if (HttpContext.Current.User.IsInRole("Administrator")) { // administrators mogen alles zien return (product) => true; } else { // chain producten zijn niet zichtbaar voor anderen return (product) => !product.Name.ToLower().Contains("chain"); } }
Listing 3: Voorbeeld QueryInterceptor Als je ook niet uit de voeten kunt met query interceptors, dan is er ten slotte nog de mogelijkheid om service-operations te schrijven. Deze kun je vergelijken met methods op een web service, waarbij de method ook toegankelijk is via de URI. ADO.NET Data Services doet de mapping van URI naar method en querystring parameters naar method parameters. Om te bepalen hoe de URI eruit ziet en op welke manier de parameters moeten worden vertaald, kun je de UriTemplate template class gebruiken. In listing 4 zie je een voorbeeld van een method die alle producten van een bepaalde vendor geeft op basis van het account nummer van de vendor. Leesoperaties moet je markeren met een WebGet attribuut, methodes die ook wijzigingen uitvoeren markeer je met een WebInvoke attribuut. In deze service operations kun je natuurlijk alle business logica kwijt die je wilt, dus ook weer services aanroepen, security checks uitvoeren, etc. Omdat ADO.NET Data Services zorgt voor de afhandeling van je methods, kun je ook in deze gevallen de querystring operaties van het framework benutten, zoals $orderby, $filter of $top. Dit werkt zolang de set die je retourneert, dit ook ondersteunt. Een belangrijke restrictie van service operations is nog wel dat er alleen primitieve types als parameters kunnen worden gebruikt. Ze moeten namelijk op de URI kunnen worden meegegeven. [WebGet( UriTemplate= "/ProductsByAccountNumber?accountNumber={accountNumber}")] public IQueryable ProductsByAccountNumber (string accountNumber) { return from productIterator in this.CurrentDataSource.Product from vendorIterator in productIterator.ProductVendor where vendorIterator.Vendor.AccountNumber == accountNumber select productIterator;
Query interceptors kun je ook stapelen, zodat je meerdere interceptors achter elkaar kunt laten uitvoeren op een entity set. Listing 3 bevat een voorbeeld van een query interceptor voor de Product entity set, waarbij voor gebruikers die niet in de Administrators rol zitten, de producten met 'chain' in de naam niet worden teruggegeven. Deze interceptors worden ook uitgevoerd bij bewerkingen. Als je een product probeert te wijzigen dat door het toegevoegde filter verdwijnt uit de set, dan resulteert dit in een foutmelding met statuscode 404 de HTTP status code voor Not Found. Het maakt ook niet uit via welke navigation properties je de betreffende entity set benadert: de interceptor wordt er altijd op losgelaten. [QueryInterceptor("Product")] public Expression> OnQueryProduct() {
58
MAGAZINE
}
Listing 4: Voorbeeld service operation Concurrency, transacties en batches Bij het zien van deze werkwijze rijst meteen ook de vraag hoe concurrency is geregeld. Als je data zo direct benaderbaar is, dan is het wenselijk om hier voorzieningen voor te hebben. Als je niets regelt, dan geldt het "last update wins" principe: de laatste update die de service ontvangt, bepaalt de staat van een entiteit. Maar ADO.NET Data Services biedt ook ondersteuning voor optimistic concurrency control. Dit doen ze door weer heel slim te lenen van de HTTP specificatie: middels E-Tags. E-Tags worden in HTTP gebruikt voor precies datgene wat hier wordt beoogd: het bewaken van concurrency door een versie-indicatie toe te voegen aan de data.
ARCHITECTURE Hiermee kunnen conditionele HTTP requests worden uitgevoerd, waarbij de E-Tag bepaalt of de bewerking plaatsvindt op de juiste versie van de data. In het klassieke voorbeeld van Order/OrderRegel zul je nooit alleen een OrderRegel willen toevoegen aan je service, maar liever een complete Order met meerdere OrderRegels. ADO.NET Data Services ondersteunt hiervoor "deep inserts" en "deep updates". Dit houdt in dat je complete objectgrafen kunt opsturen naar de server, die in de juiste volgorde worden doorgevoerd in de gegevensbron. De juiste volgorde wordt afgeleid uit de semantiek van het onderliggende entiteitenmodel. Inherent daaraan is dat deze bewerkingen transactioneel moeten worden uitgevoerd. Voor data services die op ADO Entity Framework zijn gebaseerd wordt dit automatisch geregeld. Voor andere gegevensbronnen kan dit wat lastiger liggen en zul je daar zelf in moeten voorzien. Met name bij gegevensbronnen die gedistribueerd zijn kan dit uitdagingen opleveren. De TransactionScope class uit de System.Transactions namespace kan je hierbij helpen. Naast deep inserts en updates kunnen ook batchverwerkingen worden opgestuurd. In plaats van een enkele order kunnen ook meerdere orders in één request naar een data service worden opgestuurd. Ook hier geldt dat transacties worden afgehandeld voor zover de onderliggende gegevensbron dit ondersteunt.
Bij het zien van deze werkwijze rijst meteen ook de vraag hoe concurrency is geregeld En SOA dan? We hebben gezien dat ADO.NET Data Services een krachtig en flexibel framework is om gegevens beschikbaar te stellen met een gestandaardiseerd protocol in een standaard gegevensformaat. ADO.NET Data Services richt zich met deze standaarden primair op het web, maar door de krachtige client library en de Linq-to-DataServices-provider zijn ook andere typen applicaties prima te koppelen. Tot op heden waren we in onze service oriented architecuren (SOA's) gewend om operation centric services te bouwen met een duidelijke semantiek en communicatie op basis van berichten. Operaties op business services representeren business acties en soms lopen zelfs complete processen over meerdere services heen. Nu komt daar zo'n vreemde eend in de bijt bij met een compleet andere manier van werken en een interface die de welbekende CRUD van weleer weer terugbrengt. Zijn deze twee te verenigen? Ik denk dat door het beschikbaar komen van deze technologie, door de steeds grotere adoptie van REST-styleservices en door de vlucht van RIA's er gaandeweg een mix zal ontstaan van deze twee stijlen. Hierdoor zullen traditionele SOA services op basis van SOAP steeds meer worden vermengd of aangevuld met hun RESTful tegenhangers. De keuze hierbij zou moeten gaan over het meest geschikte type interface: gaat het om data of gaat het om operaties, acties? Door de gelaagde opbouw van ADO.NET Data Services is het zelfs mogelijk om je entiteitenmodel te baseren op je bestaande set van services. Zolang je in je data service een objectlaag voor je entiteiten hebt die Linq enabled is, kun je onder water die services benaderen om de benodigde operaties uit te voeren. Als het je gaat om een getailorde interface voor RIA bovenop bestaande services, dan is een dergelijke combinatie een optie. In een hybride oplossing zou het ophalen van gegevens via een ADO.NET Data Service kunnen verlopen, vanwege de krachtige opties die je cadeau krijgt voor filtering, sortering, paging en navigatie door data, terwijl bewerkingen veel eerder door operation centric services zouden kunnen worden afgehandeld. Mijn verwachting is dat de tooling rondom ADO.NET Data Services na een eerste praktijkronde nog wat verder zal uitkristalliseren. Op dit mo-
ment staat de code generatie tool voor client side data contracten (DataSvcUtil.exe) bijvoorbeeld nog naast die voor WCF/SOAP interfaces (SvcUtil.exe). Er zal een unificatie moeten komen van deze twee modellen zodat datacontracten eventueel uitwisselbaar zijn tussen deze twee typen vanuit het perspectief van de client. Conclusie ADO.NET Data Services is een krachtige combinatie van framework en patterns waarbinnen bestaande standaarden op een slimme manier worden gecombineerd om data centric services te bouwen. REST, HTTP en Atom en zelfs LINQ zijn allemaal niet opnieuw bedacht, het heeft alleen ontbroken aan een gereedschapskist om goed en snel een dergelijk type service op te zetten. RIA's vormen het belangrijkste doelplatform voor deze technologie, maar het client framework is krachtig genoeg om ook andere typen applicaties te bedienen. Het maakt deel uit van SP1 voor .NET 3.5 (codenaam "Arrowhead"), die gepland staat voor november 2008. Tegelijk daarmee zal ook SP1 voor Visual Studio 2008 beschikbaar komen voor de bijhorende designers en IDE-integratie. Het Astoria team gaat ondertussen verder met de ontwikkeling van ADO.NET Data Services. Een erg interessante ontwikkeling is het principe van Offline ADO.NET Data Services. Het idee hierachter is dat de gegevens in een data service "offline" kunnen worden genomen en zo geschikt worden voor clients in een "occasionaly connected" scenario. Wijzigingen worden lokaal opgeslagen en komen op de service terecht zodra een netwerkverbinding weer beschikbaar is. Ook hier vindt Microsoft het wiel niet opnieuw uit, maar maakt slim gebruik van het SyncFramework, dat ook onderdeel is van .NET 3.5 SP1. Dit idee bevindt zich nog in een prototype-fase, maar wordt inmiddels ook goed ontvangen door de community. De beta versie van deze spullen is nu beschikbaar. Het is de moeite waard om er alvast mee te spelen, al was het maar om meteen kennis te maken met de slimme integratie met o.a. het ADO Entity Framework. Het bedrijf Veracity in Australie heeft het zelfs al aangedurfd om een productiesysteem te ontwikkelen met ADO.NET Data Services: FreeNatal. Dit is een open community applicatie waarin dossiers van zwangere vrouwen kunnen worden beheerd en uitgewisseld tussen verloskundigen en gynaecologen. De verwachting is dat de applicatie binnen enkele weken online zal gaan. Vermoedelijk zijn ze momenteel bezig om de applicatie te migreren van CTP bits naar SP1 Beta 1. Nog een paar maandjes wachten tot deze nieuwe telg in de .NET familie ook het levenslicht ziet. Gelukkig hebben we alvast de echo’s om naar te kijken. Referenties • ADO.NET Data Services home: http://astoria.mslivelabs.com • Project Astoria Team Blog: http://blogs.msdn.com/astoriateam/ • FreeNatal: http://www.freenatal.org • REST: http://en.wikipedia.org/wiki/Representational State Transfer
Roy Cornelissen Roy Cornelissen werkt als IT architect bij Info Support voor de business unit Industrie. Hij heeft negen jaar ervaring in de ICT en heeft gewerkt aan uiteenlopende projecten. In zijn dagelijks werk past hij Microsoft technologie toe in service georiënteerde omgevingen. Hij is te bereiken via [email protected].
magazine voor software development 59
DELPHI
Holger Flick
Delphi for .NET: Writing User-Defined Functions for Blackfish Databases Introduction I remember well when I bought my first version of Delphi in 1996. For me the number one feature at the time was the ease to build database applications without the hassle having to set up a server. Back in those days one used Paradox or dBase databases that were supported using the Borland Database Engine, the BDE. There are still a number of applications that use the BDE. However, the need to build databases in server-scenarios soon called for “more”. Interbase and FireBird soon became the database of choice for many Delphi developers. However, in order to set up any of those databases required additional work that was not required before. Now, with Delphi 2007, which is included in the RAD Studio 2007 box, CodeGear introduces a new database called “BlackfishSQL”. One characteristic of Blackfish is the fact that it is a 100% managed database written in C#. Thus, it is very easy to extend its functionality writing .NET classes with Delphi for .NET. This article will show you how to create and connect to a Blackfish database and write your own user-defined functions, i.e. functions that you can use in SQL statements (see [2] for definition). Hopefully you will see that it is very much easier to extend Blackfish databases than it has been the case using previous databases bundled with Delphi. Prerequisites In order to reproduce this hands-on article, you will need RAD Studio 2007. It does not matter, however, which SKU you have as the demo will work with Professional, Enterprise and Architect. The only difference between the different SKUs is the number of users that can connect to a database. The Blackfish license model does not restrict developers in any way. Whenever you deploy a database you need to refer to the license to make sure the required number of users can access the database you designed [1].
Checking the installation Before we begin, we need to check if BlackfishSQL is correctly installed on your developer system. You can do this very easily as the Windows Task Manager lists all running processes and the Blackfish Server Process (BSQLServer.exe) should be one of them.
Is BlackFishSQL a mature product, this being the first release? It might seem that this is the first release of Blackfish SQL and that the database is in an early state. This is wrong. BlackfishSQL is based on JDataStore, a database for Java. It is even binary-compatible, i.e. you can open JDataStore databases with Blackfish and vice-versa. This is an advantage that should not be underrated. Furthermore, this means that Blackfish is not a “new” product, but it has already a certain degree of maturity and is very much reliable because of this fact.
BlackfishSQL is based on JDataStore, a database for Java. It is even binary-compatible, i.e. you can open JDataStore databases with Blackfish and vice-versa 60
MAGAZINE
Fig. 1: Task Manager showing the BSQLServer.exe process being installed correctly
DELPHI If this is not the case, you can start the BSQLServer.exe file from the RAD Studio binary directory manually or add the service to the list of services on the system. Sadly, in some cases the RAD Studio 2007 installer does not set up the database components in the IDE and servers correctly. In the rare case that you cannot start the BlackfishSQL Server go to the CodeGear Developer Network and look for support articles that deal with installation troubleshooting for RAD Studio 2007. To make sure that the server is being started whenever Windows starts, check the settings in the Control Panel under “Administrative Settings / Services”. There should be an entry named “BlackfishSQL” with the settings shown in figure 2.
Fig. 2: Detailed information about the BlackfishSQL server service that needs to be running on the developer machine Please refer to the Blackfish SQL documentation how to configure the server settings. For example, you are able to specify a default data directory or another port the server is supposed to respond to very easily by modifying a configuration file in ini-format. Creating a database Before we can add a user-defined function to a database, we actually need a database first. For this you can use the Data Explorer of RAD Studio 2007. In the “Default Layout” you can find the Data Explorer in the same docking panel on the right where the Project Manager resides on a separate tab.
Fig. 3: The Data Explorer in the RAD Studio 2007 IDE showing all available data providers. Here, the connection nodes for BlackfishSQL have been expanded to show the sample connections that are installed.
There are several connections available for BlackfishSQL (see figure 3). In this article, we will use the Ado.Net provider. As we are on the developer machine, we do not want the extra hassle of using a server and thus use the “BlackfishSQL Local Provider”. Right-click on the name of the provider and select “Add New Connection”. In the “Add New Connection” dialog set the connection name to “ClassroomDb” and confirm all the values by clicking “OK”. Delphi will add a node for this connection which we can expand to see the contents. Furthermore, we can right-click the connection, so that we can modify the connection settings. It might seem odd as we set up the connection before actually having a database. However, this approach is being used by Interbase and Firebird as well and thus is known to a majority of database application developers. Before we can design tables and add other features of the database, we need to create it. Thus, right-click the connection and select “Modify Connection” which will make the IDE display the “Add Connection” dialog. You are supposed to provide three values: User Name, Password and Database. The user name and password are constant and have to be “SYSDBA” and “masterkey”. This article will not dive into authentication and authorization of Blackfish databases. Enter a filename for the database into the “Database” textbox, e.g. “c:\mydbs\classroom\classroom.jdb”. The extension “jdb” is rather common to be used for Blackfish databases due to its heritage, it is not mandatory though. In addition, this means that a connection to a Blackfish database always refers to a file in the file system that contains the database. Sadly, Blackfish does not only create one file, but additional temporary files and log files for transactions that are required by the database as well. I suggest you always create one directory per database so that database backup is easy. Copying the complete directory will then yield a backup of the database (BlackfishSQL has additional backup procedures that cannot be described in detail here, please refer to the documentation). If we would try to go ahead by pressing “OK” we would get an error message that the database does not exist. This is correct and thus we need to tell the IDE to create the database file by clicking on the “Advanced…” button. This will open another dialog which allows you to define all the connection settings. In the section named “Database” you find an item named “Create” which is set to “false”. Set this to “true” and the database will be created if it does not exist – it will not override an existing database. Close the Advanced dialog and press “Test Connection”. If you get the message “Test connection succeeded.” all went well and the database has been created. Using the SQL Window After setting up the connection to the database, we can browse it by expanding the node with the name of the connection (“ClassroomDb”). Furthermore, we can expand the Tables node, but no tables are listed. This yet again makes sense, as we created an empty database. You can add a table by rick-clicking the Tables node and then select “New Table” in the context menu. The IDE will display a grid that allows you to customize all the attributes for your table. We will use that in the next section after I gave some details about the example we will build. Before that we need to look at the so-called SQL Window which you can open by right-clicking on the connection and selecting “SQL Window”. The SQL Window is a new feature of RAD Studio 2007 which needs an article of its own to describe all the things you can do with it. However, in this article we will use it only to send SQL statements to the database and see the results of these. Sample Application We will build a very small application that uses the user-defined function we develop. The idea is to design a table that takes a list of students with the distance of centimetres they jumped in a sporting competition. The user-defined function will yield the same value for an American unit “feet”. Keep always in mind that you can put “anything” into a user-defined function. If you are not picky about the time it takes your database to evaluate the SQL statement, you could call a web
magazine voor software development 61
DELPHI service or do very complex calculations in a user-defined function. The size and complexity is not restricted. As mentioned right from the start, the application is thus not very difficult to write. This article will focus on the steps necessary to implement a user-defined function. But before we do that, we need to create one plain table with two attributes that stores the students and the credits they collected in the exam. Normally, one would use an Entity/Relationship model to build a database that allows administrating more than one exam of course. Creating the table can be done using the following SQL statement: CREATE TABLE students ( name VARCHAR(255), distance INT )
If you enter this statement and it is executed correctly, the IDE will show a message that says that “0 rows have been affected” and if you expand the Tables node in the Data Explorer you will be able to see the table with its attributes. We will also add some values into the table: INSERT INTO students ( name, VALUES ( 'Bob', 340 ); INSERT INTO students ( name, VALUES ( 'Holger', 330 ); INSERT INTO students ( name, VALUES ( 'Johan', 370 ); INSERT INTO students ( name, VALUES ( 'Olaf', 410 );
distance ) distance ) distance )
The implementation of the function is actually only 1 line that multiplies the centimetre value with a factor that transforms it into feet. The function also only takes one parameter, which I named “value”. In your SQL statement you will have to pass exactly one integer value to the function when using it in statements. Blackfish will take care of converting the .NET-data-types to Blackfish attribute types. When you compile the package, Delphi will create a DLL for you in the standard package output directory. Normally, you find it in the common document folder in the RAD Studio tree. On my machine here it is “C:\Dokumente und Einstellungen\All Users\Dokumente\RAD Studio”. This complicated directory name has been necessary due to Vista compatibility as Vista has very strict rules where an application can store settings and other files. Adding the UDF to the database Now we need to add the UDF to the database. We need to tell Blackfish the name of the function and in where it can find its implementation. The DLL needs to be either in the Blackfish server directory, the standard package directory, in the directory specified in the configuration file or in the Global Assembly Cache (GAC). Thus, we do not need to copy the DLL to any other directory and can add the function executing the following SQL statement in the SQL Window:
distance ) CREATE METHOD CmToFeet AS ‘UnitConverter:: Centimeters.TCentimeterConverter.ToFeet'
Now we can write the user-defined function that allows us to query the table with an additional column showing the distance in feet. Implementing the UDF As BlackfishSQL is a managed database written in C#, we can easily use Delphi for .NET to write an additional function. All functions that we want to add to our database can be stored in a single DLL, an assembly. Thus we start by creating a new Delphi for .NET Package using File / New / Others… / Delphi for .NET Projects / Package. The wizard will create the package without any units in it. We need to continue by adding a unit to the project. Right-click “Package1.dll” in the Project Manager, select “Add New / Other… / Delphi for .NET Projects / New Files / Unit”. As we have all the files now, we can set up the package by saving it on the disk. Name the package “UnitConverter” and the unit “Centimeters.pas”. In the unit, we create a class called “TCentimeterConverter”. For every user-defined function we need to define a public static function so that it can be accessed by Blackfish. So, the class that allows us to convert centimetres into feet looks like this:
In case the DLL cannot be found, you get a detailed error message where Blackfish searched for the DLL. Furthermore, you also might have to restart the Blackfish service so that it picks up the new DLL. If the statement executes successfully, you get the standard “0 rows affected.” message. Testing the UDF using the SQL Window We already added a couple of records to our table. In addition to displaying the data that is stored in the table, we also are able to display the distance in feet now. Enter the following SQL statement in the SQL Window: SELECT name, distance as cm, CmToFeet(distance) as feet FROM students
You will get the same result as shown in figure 4. The user-defined function we implemented is being called using the integer attribute “distance” as a parameter. Note that the result of the function is a floating field, which matches the “double”-type in Delphi.
unit Centimeters; interface type TCentimeterConverter = class public class function ToFeet( value: Integer ) : Double; static; end; implementation { TCentimeterConverter } class function TCentimeterConverter.ToFeet(value: Integer) : Double; begin Result := value * 0.032808399; end; end.
Listing 1: Class “TCentimeter”
62
MAGAZINE
Fig. 4: Executing a SQL statement that uses the user-defined function implemented in this article. Demo Application In addition to this article, I created a demo application that executes two SQL statements and displays the data in a grid. It is basically the same thing as using the SQL Window, however you can see how to
DELPHI connect to the Blackfish database and how to display information in VCL database controls. However, the key aspect is not visible if you look at the screenshot as the application is a Win32 VCL Forms application. Still, I can connect to a BlackfishSQL database that is fully managed and that even contains customized .NET code to realize additional functionality. The application with source code is available for download.
Conclusion This article showed in detail how to write user-defined functions in Delphi for .NET for BlackfishSQL databases. Furthermore, you have been given basic information to administrate and maintain the Blackfish service and are also able to connect and to create databases. Finally, a native 32-bit application has been shown to you that connect to the Blackfish database that contains the user-defined function. Delphi allows you to make use of a database based on managed code and still keep your code native. Thus, you can run the database in a fully managed environment on a Windows Server system and can still develop your applications in native code in Delphi. In my opinion, this is a very efficient way of developing and deploying database applications that use the full feature set of BlackfishSQL.
Holger Flick
Fig. 5: Screenshot of the demo application showing two grids. The first grid only shows columns stored in the database, the second grid displays a query that uses a user-defined function References • [1] Hodges, Nick; “Developing and Deploying with Blackfish SQL and Delphi”, URL: http://tinyurl.com/5s9zyr • [2] Wikipedia, User-defined function, http://en.wikipedia.org/wiki/User-defined_function •
Holger Flick, born and living in Germany, studied Computer Science at the University of Dortmund, Germany. His major subjects included Logical Systems, Computer Networks and Distributed Systems, Information Systems, Artificial Intelligence, Systems Analysis and Evolutionary Computation. His major interest in Delphi is ECO, which helps him develop client as well as web applications. He spoke at several Borland and CodeGear events. At the moment he works as a research assistant at the Institute of Product and Service Engineering at the Ruhr-University in Bochum.
Advertentie iAnywhere Solutions / a Sybase company
Advertentie Furore