In meinen nächsten Postings werde ich verstärkt auf LOB (Line of Business) Szenarien mit Silverlight eingehen. Die Business Anwendung liegt aktuell voll im Trend. Aus einem aktuellen Inhouse Silverlight Coaching kam die Anforderung selbst einen RIA Service zu erstellen und aus Silverlight zu konsumieren. Auf die automatische Generierung von Proxy Objekten durch eine Services Referenz aus Visual Studio sollte bewusst verzichtet werden.
Zuerst erzeuge ich in einer Silverlight Solution, genauer gesagt im Web Projekt, ein Entity Framework Datamodel aus der Nordwind Datenbank. Wie das genau geht, setze ich als Wissen voraus. Dann wird nur die Customers Tabelle ausgewählt.
Das fertige Model kann dann auf der Server Seite (nicht in Silverlight) per Code oder eben über einen WCF DataService verwendet werden. Dann wird dem Web Projekt per new Item ein WCF Dataservice Klasse hinzugefügt. Standard Dateierweiterung ist wie bei WCF üblich und Hosting im IIS SVC.
In der erzeugten Datei müssen die gelb markierten Änderungen vorgenommen werden.
Inherits DataService(Of NorthwindEntities)
Public Shared Sub InitializeService(ByVal config As DataServiceConfiguration)
config.SetEntitySetAccessRule("Customers", EntitySetRights.AllRead)
config.SetServiceOperationAccessRule("Customers", ServiceOperationRights.All)
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2
End Sub
Die Namen hängen natürlich von Ihrem Projekt ab. Ich habe hier immer die Standard Namen verwendet. Wenn mehrere Tabellen/Entitäten im Modell vorhanden sind und diese per Dataservices exponiert werden sollen, kann man mit * in einem Rutsch alles erledigen oder pro Tabelle die Rechte extra definieren. Wenn der Service funktioniert, kann man ihn direkt unter Angabe der Tabelle im Browser aufrufen ala http://localhost:40246/WcfDataService1.svc/Customers
Da der Internet Explorer XML Daten gleich formatiert, ist die Anzeige nicht direkt hilfreich. Per Rechtsclick kann man sich den Source Code allerdings anzeigen lassen.
Wie man erkennen kann, lässt sich durch ausweiten der REST Abfrage in der URL die Datenmenge einschränken. z.B. http://localhost:40246/WcfDataService1.svc/Customers('ALFKI’). Ausserdem sieht man das hier Microsoft XML Namensräume verwendet, was später noch zu mehraufwand führt.
Die nächsten Tasks werden im Silverlight Projekt durchgeführt. Zunächst wird ein Datagrid (achtung AutogenerateColumns) erzeugt.
<sdk:DataGrid Height="240" HorizontalAlignment="Left" Margin="14,10,0,0" Name="DataGrid1"
VerticalAlignment="Top" Width="374" />
<Button Content="Daten Laden" Height="25" HorizontalAlignment="Left" Margin="200,264,0,0"
Name="Button1" VerticalAlignment="Top" Width="183" />
Im Click Event des Buttons wird dann ein Web Request ausgeführt und eine Rücksprungfunktion definiert.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Handles Button1.Click
Dim wc As New WebClient
AddHandler wc.DownloadStringCompleted, AddressOf DatenLadenFertig
wc.DownloadStringAsync(
New Uri("http://localhost:40246/WcfDataService1.svc/Customers", UriKind.Absolute))
End Sub
Jetzt wird XLINQ verwendet um das XML zu parsen. Dazu braucht man eine Referenz im Silverlight Projekt auf System.XML.Linq und ein entsprechendes Imports Statement im Code. Als kleine Vorbereitung benötigt man aber vorher noch eine extra Klasse, nennen wir sie Kunden, die später als generische Liste die Datenquelle für das Datagrid darstellen wird. Kunden bekommt Eigenschaften / Propertys wir Firma, ID oder Ansprechpartner. Diese Propertys entsprechen den Feldern aus den Tabellen (müssen sie aber nicht).
Das besondere Problem sind die Namensräume. Dafür gibt es das XNamespace Element. Da bereits ein default Namespace (siehe generiertes XML von WCF Dataservice) vorhanden ist muss schon für die Auswahl aller “entry” Element ein Namensraum vorgehängt werden. Dann muss man sich durch die XML Hierarchie hangeln (Siehe XML Bild vorher) um schlussendlich an den Firmennamen zu kommen.
Private Sub DatenLadenFertig(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
Dim xml = XDocument.Parse(e.Result)
Dim n1 As XNamespace = "http://www.w3.org/2005/Atom"
Dim m As XNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
Dim d As XNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices"
Dim query = From x In xml.Descendants(n1 + "entry")
Select New kunden With {.Firma = x.Element(n1 + "content").
Element(m + "properties").Element(d + "CompanyName").Value}
DataGrid1.ItemsSource = query
End Sub
Das das funktioniert sieht man dann, wenn man die Silverlight Anwendung startet im Browser.
XML ist Case Sensitiv. Entsprechend genau muss der Code sein, sonst sieht man eventuell nur leere Zeilen oder eine Exception.
Da der Code oben nicht besonders lesbar ist und der aufwand bei mehreren Feldern stark steigt, habe ich noch eine Extension Methode geschrieben. Dazu erzeugt man im Silverlight Projekt ein Modul (VB.NET) . Mit dem Dekorator Extension erweitert man die Klasse XElement um eine neue Methode, hier Resthelper.
Public Module MyExtensions
<System.Runtime.CompilerServices.Extension()>
Public Function RestHelper(ByVal x As XElement, ByVal feld As String) As String
Dim n1 As XNamespace = "http://www.w3.org/2005/Atom"
Dim m As XNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
Dim d As XNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices"
Return x.Element(n1 + "content").Element(m + "properties").Element(d + feld).Value
End Function
Dadurch reduziert sich der Code im XLinq Query erheblich auf z.B.
Dim query = From x In xml.Descendants(n1 + "entry")
Select New kunden With {.Firma = x.RestHelper("CompanyName")}