Mit den Silverlight Ria Services kommt auch wieder eine Drag&Drop Experience auf uns Entwickler zu. Egal was man davon halten will, einen Blick ist es allemal Wert. Ich setze voraus das der geneigte Leser Ahnung von den WCF RIA Services hat. Einstiegspunkt ist die fertige Domainservice Klasse mit dem Namen DomainService1.
In Visual Studio 2010 ist dann per Menüpunkt Data, ein neuer Arbeitsbereich zu öffnen. In Datasources müssten sich dann die vorher z.B. per Entity Framework modellierten Datenobjekte befinden.
Per Drag und Drop kann man nun die ganze Tabelle auf das Silverlight Usercontrol gezogen werden. Dabei stehen die Optionen Datagrid oder Details zur Verfügung. Bei Detail werden alle Felder mit einem Label und Textbox Control dargestellt. Im Punkt Customize lässt sich auch jedes beliebige andere Silverlight Control festlegen. Auch einzeln können die Felder aufs Formular gezogen werden.
Im weiteren Beispiel habe ich ein Datagrid per Drag und Drop aus der Datasource erstellt. Der unaufregende Teil ist das Datagrid selbst.
<sdk:DataGrid AutoGenerateColumns="False" Height="200"
HorizontalAlignment="Left"
ItemsSource="{Binding ElementName=CustomersDomainDataSource, Path=Data}"
Margin="12,12,0,0" Name="CustomersDataGrid"
RowDetailsVisibilityMode="VisibleWhenSelected" VerticalAlignment="Top" Width="400">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="AddressColumn"
Binding="{Binding Path=Address}" Header="Address" Width="SizeToHeader" /> .....
</sdk:DataGrid>
Obwohl auch da schon ein wenig drinsteckt. So wird die Itemssource per Binding zugewiesen. Dazu gleich mehr. Die einzelnen Spalten werden ebenfalls per Binding Syntax gebunden. Aber an was eigentlich? An ein anderes Control, wie man im per ElementName definierten Element to Element Binding erkennen kann. Diese Control befindet sich im Ria Toolkit und wird in diesem Fall automatisch per Namensraum bekannt gemacht.
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"
Im Control DomainDataSource wird die Datenquelle definiert. Diese ist in diesem Fall als über einen Namensraum my auf die Klasse (hier eben Domainservice1 genannt) instanziert.
<riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my:Customers, CreateList=true}"
Height="0" Name="CustomersDomainDataSource" QueryName="GetCustomersQuery" Width="0">
<riaControls:DomainDataSource.DomainContext>
<my:DomainService1 />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
Zusätzlich wird per Queryname die Methode bestimmt die aufgerufen werden soll. Diese finden sich im eigentlichen Service und wird im Attribut mit dem Zusatz Query versehen. Für den aufmerksamen Leser noch kurz ein Hinweis: DesignData ist optional und hilft zur Entwurfszeit in Expression Blend und Visual Studio einen Preview auf die Daten zu bekommen.
Wenn die Abfrage Methode Parameter erwartet, werden die mit dem Element Queryparamters bestimmt.
<riaControls:DomainDataSource.QueryParameters>
<riaControls:Parameter ParameterName="CustomerID" Value="ALFKI"
/></riaControls:DomainDataSource.QueryParameters>
Das Attribut Value kann auch Werte von anderen Controls per Element Binding erhalten. Also z.B. eine TextBox als Eingabemöglichkeit für einen Filterwert. Generell kann man mit einem Filterdescriptor flexibel filtern. Der Filterwert wird in meinem Beispiel aus einer Textbox per Element Binding eingesteuert. Der Operator entspricht einer LINQ ähnlichen Syntax.
<riaControls:DomainDataSource.FilterDescriptors>
<riaControls:FilterDescriptor Operator="StartsWith"
PropertyPath="CompanyName"
Value="{Binding ElementName=TextBox1, Path=Text}"
</riaControls:FilterDescriptor>
</riaControls:DomainDataSource.FilterDescriptors>
Wenn mehrere Filterdescriptor angegeben sind, werden die logisch per AND verknüpft. Wie bei Element Binding üblich wirkt die Abfrage sofort bei Änderung des Wertes in der Textbox. Sehr spannend ist wenn man per httpfiddler auf den Traffic schaut.
GET /ClientBin/KoelnSL-Web-DomainService1.svc/binary/GetCustomers?$where=
(it.CompanyName.ToLower().StartsWith(%2522%2522)%253d%253dTrue)
Man sieht eine typische Rest Query. Die Daten selbst Binär codiert um die Datenmenge zu reduzieren. Die RIA Services übertragen also auch wirklich nur die Daten die von Client benötigt werden.
Im Browser sieht das dann so aus.
Die Query lässt sich aus noch ausbauen um z.B. eine Sortierung einzubauen.
<riaControls:DomainDataSource.SortDescriptors>
<riaControls:SortDescriptor PropertyPath="CompanyName" Direction="Ascending" />
</riaControls:DomainDataSource.SortDescriptors>
Im Datagrid wird die sortierung auch angezeigt.
Total abgefahren finde ich die Möglichkeit auch zu gruppieren. Das ist eine generelle Eigenschaft des Datagrid’s um auch z.B. Master Detail Szenarien zu lösen. In Zusammenarbeit mit der DomainDatasource geht das ganz einfach. Dazu gibt es das Element Groupdescriptor mit dem die Gruppierung festgelegt wird.
<riaControls:DomainDataSource.GroupDescriptors>
<riaControls:GroupDescriptor PropertyPath="Region" />
</riaControls:DomainDataSource.GroupDescriptors>
Das sieht dann so aus. Eine Region (hier leer mit 60 items) habe ich zugeklappt
Bei großen Datenmengen ist es natürlich besser nur wenige Datensätze zu holen. Dies ist der Benutzer gewohnt und kennt es als Paging. Das passende Control heist natürlich Datapager und befindet sich im Silverlight SDK. Ein Wermutstropfen ist, das zuerst im Dataservice eine sortierung in den Code eingebaut werden muss. Ansonsten kommt es zu seltsamen Fehlermeldungen zur Laufzeit.(The method skip is only supported for sorted input in Linq to entitys). In VB.NET sieht die Kurzform per Lambda Ausdruck.
Public Function GetCustomers() As IQueryable(Of Customers)
Return Me.ObjectContext.Customers.OrderBy(Function(t) t.CompanyName)
End Function
C# ungefähr so
public IQueryable<User> GetCustomers()
{
return this.ObjectContext.Customers.OrderBy(b => b.CompanyName);
}
Also wie gesagt, das in dem Web Projekt passieren.
So nun aber auf zum Pager im XAML Code dort wird angegeben wie groß der Bereich sein soll.
<sdk:DataPager PageSize="5"
Source="{Binding ElementName=CustomersDomainDataSource, Path=Data}"/>
Allerdings kann bzw soll man auch im DomainDatasource Control einstellungen vorgenommen werden. Interesannt ist das man mit Loadsize quasi schon ein paar Seiten vorladen, also buffern kann.
<riaControls:DomainDataSource PageSize="5" LoadSize="10"...
Die Browser Ansicht
Wichtig zu wissen: Das Paging wird per REST Querys durchgeführt. Es fließen also nur die benötigten Daten über die Leitung.
Der Benutzer kann Daten auch ganz einfach editieren. Allerdings ist es dann mit dem Paging vorbei (hier plötzlich grau im Bild) und die Daten wandern auch nicht von ganz alleine über den Domainservice in die Datenbank.
Dafür braucht man doch einen Softwareentwickler, der eine Zeile Code schreibt und submitChanges aufruft. Wo man das tut, ist eine Entscheidung des Anwendungsfalles. In meinem Beispiel wird die Methode RowEditEnded des Datagrids verwendet.
Private Sub CustomersDataGrid_RowEditEnded(ByVal sender As Object, ByVal e As System.Windows.Controls.DataGridRowEditEndedEventArgs)
CustomersDomainDataSource.SubmitChanges()
End Sub
Was soll man sagen? Es funktioniert und ist ziemlich einfach. Allerdings ist es sozusagen die Version 1. Man wird sehen.