Introductie
Sinds de introductie van Silverlight op het .NET platform is het fenomeen Rich Internet Applications (RIA) ook bij .NET ontwikkelaars aan het binnen sijpelen. Een groeiende groep ontwikkelaars heeft al één of meerdere Silverlight bedrijfsapplicaties ontwikkeld, maar er is ook een grote groep die Silverlight nog niet gebruikt voor bedrijfsapplicaties. Een van de redenen die ik vaak hoor is dat Silverlight nog niet klaar is om te worden gebruikt in bedrijfsapplicaties.
Het tegendeel is echter waar. Hoewel Microsoft Silverlight 3 nog niet alle features van WPF (Windows Presentation Foundation) bevat en de designer soms nog wel eens lastig wil doen, is het toch een volwaardig platform voor het ontwikkelen van line-of-business applicaties. Dit zal met de komst van .NET RIA Services alleen nog maar verbeteren. Met het .NET RIA Services framework kun je eenvoudiger bedrijfsapplicaties ontwikkelen op basis van een SOA architectuur doordat de RIA services op een aantal gebieden veel werk uit handen neemt.
Aan de hand van vier aandachtsgebieden laat ik zien welke features het .NET RIA Services framework bevat om voor die aandachtsgebieden een passende oplossing te bieden. Maar eerst een korte uitleg over het framework zelf.
Wat zijn .NET RIA services
Kort nadat Silverlight 3 is gelanceerd, heeft Microsoft een CTP (Community Technology Preview) versie van .NET RIA services gelanceerd.
Het .NET RIA Services framework voegt een tweetal solution templates toe aan Visual Studio. Een ervan is de Silverlight Business Application template. De andere is de .NET RIA services class library template. Wanneer je een solution start op basis van de Silverlight Business Application template wordt een solution gegenereerd die een ASP.NET web applicatie bevat en een Silverlight-applicatie. Deze zijn aan elkaar gekoppeld door Visual Studio, zodat services die worden gedefinieerd in de web applicatie automatisch beschikbaar komen in de Silverlight-applicatie. De applicatiestructuur die hierdoor ontstaat is in Figuur 1 schematisch weergegeven.

Figuur 1: Applicatiestructuur na toevoeging van de Silverlight koppeling
De koppeling tussen de webapplicatie en de Silverlight-applicatie via een RIA link.
De koppeling tussen de webapplicatie en de Silverlight-applicatie wordt gevormd door een zogenaamde RIA link. In het Silverlight-project is middels een buildproperty een verwijzing opgenomen naar het webproject. Daarnaast is er een MSBuild target geïnstalleerd met het framework die ervoor zorgt dat automatisch de benodigde code voor de client wordt uitgegenereerd als je de solution build. Tot slot wordt er in de solution een afhankelijkheid geïnjecteerd die ervoor zal zorgen dat het webproject altijd voor het Silverlight-project wordt gecompileerd. Op het moment dat je een nieuwe service toevoegt aan het webproject en de solution build zal Visual Studio automatisch voor je een clientproxy aanmaken met de bijbehorende types voor gebruikte entiteiten waar je dan gebruik van kan maken in de Silverlight-applicatie.
Bedrijfsgegevens ontsluiten
Om met gegevens te werken in .NET RIA services kan er gebruik worden gemaakt van zogenaamde domainservices. Een domainservice is verantwoordelijk voor het beheer van het gehele gegevensdomein in de applicatie of een deel ervan. Om een domainservice te implementeren voeg je een class toe aan het webproject die je laat afleiden van het DomainService type. Om te zorgen dat er tijdens het builden van de solution een clientproxy wordt uitgegenereerd, zul je de class ook nog moeten voorzien van het EnableClientAccess attribuut.
Een domainservice is verantwoordelijk voor het beheer van het gehele gegevensdomein in de applicatie
In de voorbeeldapplicatie heb ik gebruik gemaakt van een ADO.NET entity model om een database met order, facturatie en productgegevens te ontsluiten. Het entity model is overigens niet verplicht, je kunt een data access technologie naar keuze gebruiken. Om gegevens in de Silverlight applicatie te kunnen gebruiken voeg je een nieuwe Domain Service Class toe aan het webproject. Visual Studio toont een dialoogvenster waarin je een keuze kan maken uit de entiteiten die je beschikbaar wilt via de domainservice. In het dialoogvenster worden vervolgens alle entiteiten getoond die beschikbaar zijn in het ADO.NET entity model. In Figuur 2 staat een schermafdruk van dit dialoogvenster.

Figuur 2: Dialoogvenster voor het toevoegen van een domainservice
Nadat er een keuze is gemaakt voor Invoice en InvoiceItem zal Visual Studio een class uitgenereren, met daarin alle benodigde logica om met facturen en factuur items te werken. Je kan hierna direct beginnen met het implementeren van schermen. Een groot voordeel is dat ik nu niet met de hand de basis van een service moet implementeren. Dit kan namelijk een tijdrovende klus zijn, waarbij waarschijnlijk veel geknipt en geplakt gaat worden in de code. Bijna elke bedrijfsapplicatie werkt met select-, insert-, update- en delete-acties op een gegevensbron waarbij verschillende bedrijfsregels moeten worden afgedwongen in de update scenario’s en eventueel filtering bij de selecties. Door het gebruik van .NET RIA Services wordt het standaardwerk je uit handen genomen en kun je de tijd die je daarmee bespaart besteden aan de meer complexe zaken, waaronder de bedrijfslogica.
Wanneer de domainservice uit het voorbeeld is uitgegenereerd door Visual Studio heb je een service die voor Invoice en InvoiceItem insert-, update- en delete acties kan uitvoeren. Ook is er een methode uitgegenereerd die het mogelijk maakt om Invoice en InvoiceItem entiteiten op te halen uit de database.
Domainservice Class is aanpasbaar na generatie
Hoewel de hele domainservice class automatisch is uitgegenereerd, is het niet zo dat je als ontwikkelaar hier niets meer aan kan veranderen. Als de service eenmaal is uitgegenereerd kun je hier zelf aangepaste queries aan toevoegen. Je kan deze queries toevoegen aan de gegenereerde file. Wanneer je later nogmaals de code voor de service opnieuw wilt uitgenereren, is het verstandig om gebruik te maken van de mogelijkheden met partial classes. Op het moment dat je de gegenereerde functionaliteit nogmaals wilt genereren kun je de file die eerder is uitgegenereerd verwijderen. Daarna kun je opnieuw de wizard voor een nieuwe domainservice uitvoeren om het verwijderde deel van de domainservice opnieuw te laten genereren.
Er zijn verschillende manieren waarop je een domainservice kan aanpassen. Zo kun je de selectiefunctionaliteit van de domainservice uitbreiden door een methode te schrijven die een IQueryable<T> type oplevert. Als voorbeeld heb ik een methode geschreven die een set producten uit een bepaalde productcategorie ophaalt. Deze methode accepteert een ID van een product categorie als invoer. De naamgeving van de methode kun je als ontwikkelaar zelf bepalen, je bent hierbij niet gebonden aan enige naamgevingsconventie van het framework. De implementatie van deze methode zie je in codevoorbeeld 1.
public IQueryable<Product> FindProductsByCategoryId(long categoryId)
{
return this.Context.Products.Where(
item => item.ProductCategory.ProductCategoryId == categoryId);
}
Codevoorbeeld 1: Een set producten ophalen uit een productcategorie
Binnen de methode kan er gebruik worden gemaakt van een LINQ expressie om een selectie te maken uit de entiteiten die beschikbaar zijn gemaakt via de Context property van de domain service class. Hierbij zal Visual Studio je voorzien van hints middels intellisense wat het werken aan een dergelijke methode een aangename klus maakt.
In tegenstelling tot de selectie methodes zijn de methodes voor insert, update en delete wel gebonden aan een naamgevingconventie. Voor insert operaties is dit Insert<Entity>(Entity e), voor de update en delete operaties is hetzelfde patroon gekozen, namelijk Update<Entity>(Entity e) en Delete<Entity>(Entity e). De inhoud van deze methodes is in tegenstelling tot de naam wel aan te passen, zodat je bijvoorbeeld automatisch de datum waarop een product is aangepast kan invullen. In codevoorbeeld 2 staat een voorbeeld van een dergelijke aanpassing.
public void UpdateProductCategory(ProductCategory currentProductCategory)
{
currentProductCategory.ModifiedBy =
Thread.CurrentPrincipal.Identity.Name;
currentProductCategory.DateModified = DateTime.Now;
this.Context.AttachAsModified(currentProductCategory,
this.ChangeSet.GetOriginal(currentProductCategory));
}
Codevoorbeeld 2: Een productcategorie bijwerken
Elke domainservice die je toevoegt aan de webapplicatie komt beschikbaar als WCF service en kan dus ook met bijvoorbeeld een Windows Forms client worden benaderd. Alle configuratie van de WCF service wordt hierbij door het framework geregeld.
Wanneer je .NET RIA Services gaat gebruiken in een andere omgeving dan Silverlight, zul je merken dat Microsoft hiervoor geen templates of componenten heeft meegeleverd en dat het werken met domainservices meer handmatig werk is. In Silverlight heeft Microsoft ervoor gezorgd dat je gebruik kan maken van de mogelijkheden die LINQ biedt om gegevens op te vragen. De vertaling van de LINQ queries naar een call voor de domainservice wordt door het framework gedaan. Daarnaast is ondersteuning voor het intelligent verwerken changesets door de domainservice, zodat het toevoegen, wijzigen of verwijderen van meerdere entiteiten met één aanroep naar de domainservice kan worden opgelost.
Bedrijfsgegevens weergeven in Silverlight
De gegevens die je ophaalt uit een domainservice kun je in Silverlight door middel van databinding weergeven in de user-interface, daarbij kun je na genoeg alles koppelen aan user-interfacecomponenten. Je hebt als ontwikkelaar alle mogelijkheden van het Silverlight platform beschikbaar om de data te presenteren aan de eindgebruiker. Dit heeft als groot voordeel dat je de gebruiker van je RIA applicatie meer opties kan bieden om met bedrijfsgegevens te werken. Het komt steeds vaker voor dat de gebruiker niet langer genoeg heeft aan een lijst met gegevens. Ik heb zelf al eens meegemaakt dat gebruikers een tabel met gegevens over een patiënt ook graag als tijdsbalk wilden kunnen inzien, om op die manier een beter beeld kregen van de situatie van deze patiënt over de tijd bekeken. Met technieken als ASP.NET is dit een flinke uitdaging en wordt het al snel ondoenlijk om hetzelfde te implementeren. Silverlight heeft als voordeel dat de layout mogelijkheden veel uitgebreider zijn dan die van ASP.NET en daarnaast het databinding model veel beter in staat is om gegevens op de gewenste manier te koppelen aan de user interface elementen, ook al betreft het geen tekst, maar bijvoorbeeld een afbeelding.
.NET RIA services breidt het databinding model van Silverlight nog verder uit. Je kan volledig in XAML (De opmaaktaal voor user-interfaceelementen in Silverlight) beschrijven hoe de gegevens die je wilt weergeven moeten worden opgehaald uit een domain service en gepresenteerd aan de gebruiker. Om gegevens uit een domain service te koppelen aan één of meerdere user-interfacecomponenten kan je gebruik maken van een DomainDataSource-object in de XAML file voor de user interface. Een voorbeeld van een dergelijke DomainDataSource is in Codevoorbeeld 3 weergegeven.
In XAML beschrijven hoe de weer te geven gegevens worden opgehaald uit een domain service en gepresenteerd aan de gebruiker.
De DomainDataSource control is gekoppeld aan de ProductsEnityContext. Dit is de gegenereerde de clientproxy voor de Products domainservice die zich in de voorbeeldwebapplicatie bevind. Alle clientproxies die worden uitgegenereerd kunnen worden teruggevonden in dezelfde namespace als waar je ze in hebt gezet in het webproject.
Met het attribuut QueryName kun je aangeven welke methode je wilt gebruiken om gegevens op te halen uit de domainservice. Als waarde voor het QueryName attribuut heb ik aangegeven dat ik gebruik wil maken van de FindProductsByCategoryId methode om productgegevens op te halen. Deze methode verwacht een parameter categoryId en zal als resultaat een set producten opleveren uit de opgegeven productcategorie. Om te zorgen dat er een waarde voor deze parameter wordt meegegeven aan de domainservice, heb ik een controlparameter geconfigureerd op de DomainDataSource die ervoor zorgt dat het ID van het geselecteerde item uit een dropdownlijst met productcategorieën wordt doorgegeven. Je kunt naast controlparameters ook normale parameters toevoegen aan de DomainDataSource. De waarde hiervan kun je vervolgens in code opgeven.
Bij een controlparameter kun je naast de parameternaam en de te gebruiken control ook aangeven welk event op de control ervoor zorgt dat de gegevens in de DomainDataSource worden ververst. Zo kun je bijvoorbeeld het SelectionChanged event van de dropdownlijst met productcategorieën koppelen. Zodra de gebruiker een andere productcategorie selecteert in de lijst zal het SelectionChanged event afgaan en de gegevens verversen.
<riaControls:DomainDataSource x:Name=”ProductsDataSource" AutoLoad="True"
QueryName="FindProductsByCategoryId">
<riaControls:DomainDataSource.DomainContext>
<ds:ProductEntityContext
xmlns:ds="clr-namespace:SilverlightShop.Web.Services" />
</riaControls:DomainDataSource.DomainContext>
<riaControls:DomainDataSource.QueryParameters>
<riaData:ControlParameter
ControlName="ProductCategoriesDropDown"
ParameterName="categoryId"
PropertyName="SelectedItem.ProductCategoryId"
RefreshEventName="SelectionChanged"/>
</riaControls:DomainDataSource.QueryParameters>
</riaControls:DomainDataSource>
Codevoorbeeld 3: Een DomainDataSource voor producten uit een specifieke productcategorie
Om de gegevens uit de DomainDataSource in Codevoorbeeld 3 weer te geven in een tabel kun je gebruikmaken van een DataGrid. Dit component is onderdeel van Silverlight 3. In Codevoorbeeld 4 is te zien hoe je doormiddel van een elementbinding de ItemsSource van een DataGrid kan koppelen aan de Data van de DomainDataSource in Codevoorbeeld 3.
<data:DataGrid x:Name="ProductsGrid"
ItemsSource="{Binding ElementName=ProductsDataSource,Path=Data,Mode=OneWay}"
AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="Code"
Binding="{Binding Code}"/>
<data:DataGridTextColumn Header="Name"
Binding="{Binding Name}"/>
<data:DataGridTextColumn Header="Price"
Binding="{Binding Price}"/>
<data:DataGridTextColumn Header="Created by"
Binding="{Binding CreatedBy}"/>
<data:DataGridTextColumn Header="Date created"
Binding="{Binding DateCreated}"/>
<data:DataGridTextColumn Header="Modified by"
Binding="{Binding ModifiedBy}"/>
<data:DataGridTextColumn Header="Date modified"
Binding="{Binding DateModified}"/>
</data:DataGrid.Columns>
</data:DataGrid>
Codevoorbeeld 4: Een DataGrid met productinformatie uit de DomainDataSource met productinformatie
Door middel van het AutoLoad attribuut op de DomainDatasource heb ik ervoor gezorgd, dat bij het laden van het scherm automatisch de gegevens worden opgehaald door de DomainDataSource. Hierdoor hoef ik zelf geen code te schrijven om de gegevens in het scherm bij te werken. Alles wordt door het .NET RIA services framework geregeld. Wanneer je gebruik maakt van het AutoLoad attribuut en er is tijdens het laden van het scherm geen waarde beschikbaar voor één of meerdere parameters op de DomainDataSource, zal er een standaardwaarde worden ingesteld voor deze parameter. Wil je meer controle over dit gedrag dan kun je het beste zelf code schrijven om de gegevens van de DomainDataSource te vernieuwen. Je kan hiervoor gebruik maken van de Load methode op de DomainDataSource class.
Naast het ophalen van een set gegevens kun je de gegevens ook sorteren op de DomainDataSource. Dit doe je door op het DataGrid het attribuut CanUserSortColumns met de waarde True op te nemen. Meer is er niet nodig om de gebruiker in staat te stellen om de gegevens in de tabel te sorteren. De DomainDataSource neemt in combinatie met de domainservice de gehele sorteeroperatie op zich, zonder dat we daarvoor aparte sorteercode hoeven te schrijven. Het sorteren vindt plaats op de server. Wanneer je in de domainservice al gegevens sorteert zal deze sortering worden gecombineerd met dat datgene wat de client aan sortering doorgeeft.
Naast het ophalen van een set gegevens kun je de gegevens ook sorteren op de DomainDataSource
Tot slot ondersteunt de DomainDataSource nog het instellen van een of meerdere FilterDescriptors. Hierbij kun je als ontwikkelaar zelf een aantal filters opgeven waarop de gegevens uit de domainservice moeten worden gefilterd. Je kan deze FilterDescriptors ook koppelen aan bijvoorbeeld een tekstvak. Je kan dan zelf een filter instellen door bijvoorbeeld de naam van een product in te vullen in het tekstvak. Net als bij het sorteren vindt ook het filteren plaats op de server. Deze wordt eventueel gecombineerd met filters die je eventueel al in de domainservice hebt toegepast.
Wat het werken met .NET RIA services in Silverlight zo prettig maakt, is het feit dat je als ontwikkelaar voor het weergeven van gegevens helemaal geen C# code nodig hebt. Dit heeft als voordeel dat je dit werk ook door een grafisch vormgever kan laten uitvoeren. Als ontwikkelaars hebben we nu eenmaal meer gevoel bij code en minder bij de grafische vormgeving. Dankzij de komst van gereedschap als Expression Blend en de komst van .NET RIA services zijn we nu in staat om meer samen met een interaction designer samen te werken aan het zelfde programma. Alle codevoorbeelden kunnen namelijk ook prima in Expression Blend door een grafisch vormgever worden opgezet. Het eindresultaat zal hierdoor gebruiksvriendelijker zijn en er direct ook aantrekkelijk uitzien voor de gebruikers.
Bedrijfsgegevens bewerken in Silverlight
Hiervoor heb ik laten zien dat we zonder een letter C# code te schrijven in staat zijn om gegevens te tonen in een Silverlight user interface. Maar met alleen gegevens weergeven hebben we maar een halve bedrijfsapplicatie. Je wilt de gegevens ook kunnen wijzigen.
In de voorbeeldapplicatie kan een gebruiker producten toevoegen, bewerken en verwijderen. Hiervoor zijn twee opties beschikbaar. Je kan met de hand een detailscherm ontwerpen waarbij je volledig vrij bent in de manier waarop je de gegevens presenteert. Maar heb je alleen een detailscherm nodig en stel je niet veel eisen aan de weergave van de velden, dan kun je er ook voor kiezen om gebruik te maken van de DataForm control. Deze control biedt standaard detail-, insert-, update- en deletemogelijkheden, die in veel gevallen goed toepasbaar zijn.
Als voorbeeld heb ik een DataForm gemaakt voor het kunnen bewerken van producten uit de lijst die ik in Codevoorbeeld 3 en 4 heb weergegeven in de user-interface. In Codevoorbeeld 5 is het volledige formulier weergegeven voor het bewerken van een enkel product.
<dataFormToolkit:DataForm x:Name="ProductDetailsForm" Grid.Row="4"
ItemsSource="{Binding ElementName=ProductsDataSource,
Path=Data}"
CurrentItem="{Binding ElementName=ProductsGrid,
Path=SelectedItem}">
<dataFormToolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataFormToolkit:DataField Label="Code">
<TextBox Text="{Binding ValidatesOnExceptions=True,
NotifyOnValidationError=True,Path=Code,Mode=TwoWay}"/>
</dataFormToolkit:DataField>
<dataFormToolkit:DataField Label="Name">
<TextBox Text="{Binding ValidatesOnExceptions=True,
NotifyOnValidationError=True,Path=Name,Mode=TwoWay}"/>
</dataFormToolkit:DataField>
</StackPanel>
</DataTemplate>
</dataFormToolkit:DataForm.EditTemplate>
</dataFormToolkit:DataForm>
Codevoorbeeld 5: Detailscherm voor productinformatie
Om gegevens te kunnen weergeven en bewerken heb ik als ItemsSource de eerder aangemaakte DomainDataSource opgegeven. Dit is in principe al voldoende om gegevens te kunnen toevoegen, bewerken en verwijderen. Maar om het voor de gebruiker nog een stuk beter te laten werken heb ik het CurrentItem attribuut gekoppeld aan het SelectedItem attribuut van het eerder aangemaakte DataGrid. Als de gebruiker een product uit de lijst selecteert, wordt automatisch in het detailscherm het geselecteerde product getoond en kan hij direct beginnen met bewerken.
Door gebruik te maken van het DataForm control, geef je voor een groot deel de presentatie van gegevens uit handen. Deze presentatie kun je nog bijstellen door bijvoorbeeld een EditTemplate op te nemen. Hierbij kun je niet alleen aangeven welke control moet worden gebruikt om een bepaald veld weer te geven, maar je kun je ook aangeven welk label erbij moet worden getoond. Het is niet verplicht om gebruik te maken van een EditTemplate. Wanneer er helemaal geen controle nodig is over de manier waarop velden worden weergegeven en welke velden worden weergegeven kun je er ook voor kiezen het attribuut AutoGenerateFields met de waarde True op te nemen. In dat geval wordt alles automatisch geregeld.
Het voordeel van het gebruik van de DataForm control is dat het minder tijd kost om een user interface op te zetten voor het kunnen bewerken van gegevens. Dankzij het databinding model van Silverlight zijn we zelfs in staat om ook de iets complexere schermen met de DataForm control op te maken.
De gegevens opslaan
Nu we een scherm hebben om gegevens te bewerken, is de volgende stap het opslaan van deze gegevens. Elke domainservice werkt met een unit-of-workpatroon. Alle wijzigingen die de gebruiker aanbrengt in de producten worden in de Silverlight applicatie verzameld. Pas als we de SaveChanges() methode aanroepen op de ProductEntityContext, de gegenereerde proxy voor de domain service, worden deze wijzigingen doorgestuurd naar de domainservice.
Om de wijzigingen op te kunnen slaan heb ik aan het producten scherm een extra knop toegevoegd. Als een gebruiker op deze knop klikt zal de applicatie kijken of er wijzigingen zijn en deze opsturen naar de domainservice. weergegeven. In de CTP versie die ik heb gebruikt om het een en ander uit te proberen zit een fout, waardoor het uitvoeren van de SubmitChanges methode zonder dat er wijzigingen zijn, resulteert in een exception. Daarom heb ik in de code een extra controle toegevoegd die bepaald of er wijzigingen zijn. In de releaseversie is dit probleem opgelost en is de extra controle niet langer nodig. De code voor de EventHandler van deze knop is in Codevoorbeeld 6.
private void SubmitChangesButton_Click(object sender, RoutedEventArgs e)
{
ProductEntityContext context =
(ProductEntityContext)ProductsDataSource.DomainContext;
if (context.HasChanges)
{
context.SubmitChanges();
}
}
Codevorbeeld 6: Eventhandler voor het opslaan van gegevens
Data validatie
Er is niets vervelender voor de gebruiker dan een applicatie die een onverklaarbare foutmelding geeft als hij of zij toevallig een veld is vergeten te vullen in het scherm. Maar het is ook zeker heel vervelend voor de ontwikkelaar, je moet zonder validatie aan de voorkant ineens rekening gaan houden met foutieve gegevens die binnenkomen in de domain service.
Het .NET RIA services framework maakt gebruik van een set attributen uit het .NET framework. Sinds .NET 3.5 service pack 1 is er een nieuwe namespace toegevoegd aan het .NET framework met de naam System.ComponentModel.DataAnnotations. In deze namespace zitten attributen om extra metadata toe te voegen aan een objectmodel, waarmee dit model bijvoorbeeld kan worden gevalideerd. Er zitten ook attributen in om automatisch een label te kunnen toevoegen aan een veld. Dezelfde namespace is ook toegevoegd aan Silverlight 3, je zult dan ook merken dat veel kennis die je wellicht met het .NET framework al hebt opgedaan kan hergebruiken voor het bouwen van applicaties in Silverlight.
Om validatie toe te voegen zul je de entiteiten in je webproject moeten voorzien van validatieattributen. Deze attributen zullen na het compileren automatisch in de Silverlight applicatie terecht komen en ervoor zorgen dat de invoer van de gebruiker wordt gecontroleerd op fouten.
Alle enteiten in de web applicatie zijn gedefinieerd als partial class.
Alle enteiten in de web applicatie zijn gedefinieerd als partial class. Hierdoor kunnen we zelf nog uitbreidingen toevoegen aan deze class files door in een aparte C# code file de class opnieuw te definiëren en ook als partial te markeren. Op dit tweede deel van de class zetten we vervolgens een enkel attribuut neer met de naam MetadataType. Hier geven we als parameter een ander type aan mee die alle metadata voor de entiteit bevat. Op dit metadata type hoeven alleen de eigenschappen te worden gedefinieerd waaraan we extra metadata willen toevoegen.
Als voorbeeld hiervan heb ik een aantal attributen toegevoegd aan de Product class. Ik heb hiermee bepaald dat de naam verplicht is en slechts 50 karakters lang mag zijn. De code hiervan is in Codevoorbeeld 7 weergegeven.
[MetadataType(typeof(ProductMetadata))]
public partial class Product : EntityObject { }
public class ProductMetadata
{
[Required(ErrorMessage = "De naam van het product is verplicht")]
[StringLength(50, ErrorMessage = "Naam mag maximaal 50 karakters bevatten.")]
public string Name { get; set; }
}
Codevoorbeeld 7
Nadat de solution een keer opnieuw is gecompileerd, kun je het effect direct zien in de user interface als je de applicatie start en probeert om een product op te voeren met een ongeldige naam. Silverlight zal automatisch een rode rand rondom het veld met de ongeldige gegevens zetten met daarbij de door mij opgegeven foutmelding (Zie Figuur 3).

Figuur 3: Het productdetailsscherm met foutieve invoer
Authenticatie en autorisatie
Je kan met een combinatie van domainservices en de standaard user-interfacecomponenten al een bedrijfsapplicatie implementeren. Het enige dat ontbreekt is een manier waarop je de applicatie kan beveiligen tegen gebruik door onbevoegden.
Om je bedrijfsapplicatie te beveiligen tegen misbruik biedt Silverlight in combinatie met RIA services een goede oplossing. Je kan kiezen tussen Windows- en Formsauthenticatie om gebruikers aan te melding in de applicatie. Waarschijnlijk ken je deze twee authenticatiemogelijkheden al uit ASP.NET. Dit is ook precies waar het .NET RIA Services framework gebruik van maakt. Ook hier kun je weer gebruik maken van de kennis die je wellicht al hebt van ASP.NET. Daarnaast is het zo dat je hierdoor bestaande ASP.NET applicaties kan uitbreiden met een Silverlight gedeelte, zonder dat je daarvoor heel veel moet wijzigen in de webapplicatie.Je kan gebruik maken van de reeds bestaande authenticatiemogelijkheden en rechtenstructuur van de webapplicatie.
Gebruikers authenticeren
Om te voorkomen dat gebruiker zomaar gebruik maken van de Silverlight-applicatie kun je een authenticatiecomponent configureren. Hierbij kun je gebruik maken van zowel het windowsAuthentication component als het FormsAuthentication component.
Als voorbeeld heb ik gebruik gemaakt van forms authenticatie. Om formsauthenticatie aan te zetten zet je in het bestand app.xaml de authentication service op forms authentication. (de Windows authentication variant staat in commentaar er onder) De configuratie hiervoor is in Codevoorbeeld 8 weergegeven.
<Application.ApplicationLifetimeObjects>
<app:RiaContext>
<app:RiaContext.Authentication>
<appsvc:FormsAuthentication/>
<!--<appsvc:WindowsAuthentication/>-->
</app:RiaContext.Authentication>
</app:RiaContext>
</Application.ApplicationLifetimeObjects>
Codevoorbeeld 8: Authenticatieservice instelling voor de applicatie
Voor het aanmelden is er een apart loginscherm gedefinieerd. De gebruiker kan hierin een gebruikersnaam en wachtwoord opgeven waarmee hij zich wil aanmelden in de applicatie. Als de gebruiker op de login knopt klikt is het de bedoeling dat hij wordt aangemeld en het hoofdscherm te zien krijgt. De code achter de login knop is in Codevoorbeeld 9 weergegeven.
private void LoginButton_Click(object sender, RoutedEventArgs e)
{
LoginOperation _authOp;
_authOp = _RiaContext.Current.Authentication.Login(
this.loginUserNameBox.Text,
this.loginPasswordBox.Password);
_authOp.Completed += LoginOperation_Completed;
}
Codevoorbeeld 9: Aanmeldprocedure in de Silverlight-applicatie
Geheel in de traditie van Silverlight kun je de Login methode niet synchroon afhandelen. In plaats hiervan wordt er een LoginOperation instantie teruggegeven door de Login methode waar je het Completed event van kunt koppelen aan een methode in de applicatie. Deze methode wordt aangeroepen als het aanmelding is voltooid. Je kan dan in deze methode het scherm goed zetten zodat de gebruiker aan het werk kan met de applicatie.
Om het aanmelden helemaal af te maken zul je de web applicatie zodanig moeten configureren dat deze gebruik maakt van ASP.NET forms authentication. Daarnaast heb je een implementatie van een AuthenticationService nodig. Wanneer je gebruik maakt van de standaard Business Applicatie template in Visual Studio is er al een standaard implementatie van het AuthenticationService-component in het project aanwezig. Een voorbeeld van hoe een basisimplementatie van een authenticationservice eruit ziet staat in Codevoorbeeld 10.
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User>
{
}
public class User : UserBase
{
// NOTE: Profile properties can be added for use in Silverlight application.
// To enable profiles, edit the appropriate section of web.config file.
// public string MyProfileProperty { get; set; }
}
Codevoorbeeld 10: Standaardimplementatie authenticatieservice
Je mag slechts één implementatie van de authenticationservice hebben in je webproject. Tijdens het compileren wordt namelijk automatisch een relatie gelegd tussen de authenticatie logica in de Silverlight-applicatie en de webapplicatie.
De informatie die de gebruiker opgeeft in de user-interface om zich aan te melden wordt onbeveiligd naar de server verzonden. Het is dan ook belangrijk om te zorgen dat de server waarop je applicatie komt te draaien alleen via een SSL verbinding is te benaderen. Dit voorkomt dat onbevoegden de gebruikersnaam en wachtwoord combinatie van de gebruikers onderscheppen en deze misbruiken om zich aan te melden in de applicatie.
Gebruikers autoriseren
Met het authenticeren voorkom je dat onbekende gebruikers gebruik kunnen maken van je applicatie. De tweede stap is het autoriseren, waarbij we bepalen wat de gebruikers van de applicatie wel en niet mogen doen in de applicatie.
Toegang tot functionaliteit in de applicatie kun je op twee locaties instellen. De eerste locatie is op de domainservices zelf. Hier kun je aangeven middels het RequiresAuthentication-attribuut dat een gebruiker aangemeld moet zijn om gebruik te maken van een operatie op de domainservice. Dit kan je nog verder verfijnen door het RequiredRoles-attribuut toe te voegen. Met dit attribuut kun je aangegeven welke rollen een gebruiker moet vervullen om gebruik te kunnen maken van de operatie. Een voorbeeld van autorisatie van een service operatie is in Codevoorbeeld 11 weergegeven.
[RequiresAuthentication]
[RequiresRoles("Order beheerder")]
public IQueryable<OrderItem> FindOrderItemsByOrderId(long orderId)
{
return this.Context.OrderItems.Where(item => item.Order.OrderId == orderId);
}
Codevoorbeeld 11: Authorisatie doormiddel van attributen
De tweede locatie waar gebruik kan worden gemaakt van autorisatie om gebruikers al dan niet toegang te geven tot een specifiek deel van de applicatie is in het Silverlight gedeelte van de bedrijfsapplicatie. Hier kan gebruik worden gemaakt van de mogelijkheden van het User object dat beschikbaar is via de RiaContext in de applicatie. Je kan bijvoorbeeld bepalen of de gebruiker een bepaalde rol vervuld door de IsInRole methode aan te roepen op het User object. Een voorbeeld hiervan is in Codevoorbeeld 12 weergegeven.
if (RiaContext.Current.User.IsInRole("Administrator"))
{
// Voer de actie voor de beheerder uit
}
Codevoorbeeld 12: Controle of de gebruiker een administrator is
De controle of de gebruiker in een bepaalde rol zit wordt clientside uitgevoerd. Wanneer je gebruik maakt van deze vorm van toegangscontrole zul je in de domainservice extra maatregelen moeten nemen om te voorkomen dat er alsnog toegang kan worden verkregen tot data uit de service via een andere client. Dit kan bijvoorbeeld door de IsInRole methode aan te roepen op het Thread.CurrentPrincipal object.
Doordat je op twee locaties kan aangeven welke gebruikers toegang hebben tot bepaalde functionaliteit kun je de applicatie van voor tot achter goed beveiligen tegen onbevoegd gebruik. Er is goed nagedacht over de programmeerinterface waardoor het niet heel veel zoekwerk is om de juiste combinatie van rechten en controles in de applicatie te bouwen zodat gebruikers niet zomaar overal bij kunnen.
Tot slot
Microsoft heeft met .NET RIA services een volgende grote stap gezet om van Silverlight een serieus applicatieplatform te maken. De mogelijkheden van .NET RIA services zijn flexibel genoeg om een ontwikkelaar alle ruimte te geven, zodat deze zelf kan bepalen wat binnen zijn architectuur de beste oplossing is voor een deel van zijn applicatie of de hele applicatie.
Op dit moment is er voor zowel Visual Studio 2008 als voor Visual Studio 2010 een testversie te downloaden van het .NET RIA services framework. In de komende maanden zullen er meer testversies beschikbaar worden gesteld en zal er uiteindelijk een release volgen van deze componenten. Ik wil dan ook iedere ontwikkelaar die met Silverlight gaat werken aanmoedigen om ook .NET RIA services uit te proberen en hun feedback op te sturen naar Microsoft.