Im letzten Post haben wir schon über das API gesprochen, um mittels LINQ auf Bing Suchergebnisse zugreifen zu können. In diesem Tutorial werden wir über .NET RIA Service Integration. Das IQuerable und das LINQ-Pattern sind grundlegendes des .NET RIA Service, welches den Entwicklern erlaubt übergreifend über Client und Server auf Daten zugreifen zu können.
Normalerweise verwendet man Bing für eine Rich Internet Application auf eine Art: Man macht Client-seitig einen Anfrage an Bing durch den Server oder direkt vom Client, unter der Verwendung einer Cross-Domain Networking API, wie sie von Silverlight zur Verfügung gestellt wird.
Für Demonstrationzwecke reicht eine einfach und minimalistische Applikation. Es reicht eine Search TextBox und einige Panels um die Ergebnisse anzuzeigen gemeinsam mit dem Paging UI. Das UI kann man für beide Möglichkeiten der Abfragen verwenden.
Zugriff auf Bing durch einen Server Proxy
Als erstes die Suchanfrage unter der Verwendung von LINQ to Bing API mit einem DomainService, welches auf dem Server läuften und das reguläre .NET RIA Service tooling um einen DomainContext am Client zu generieren. Hier eine einfache grundlegende DomainService Klasse, welche die Suchfunktionalität abkapselt:
[EnableClientAccess]
<span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">class</span> SearchService : DomainService {
<span style="color: rgb(0, 0, 255);">private</span> BingContext CreateContext() {
<span style="color: rgb(0, 0, 255);">string</span> appKey = ConfigurationManager.AppSettings[<span style="color: rgb(0, 96, 128);">"BingAppKey"</span>];
<span style="color: rgb(0, 0, 255);">return</span> <span style="color: rgb(0, 0, 255);">new</span> BingContext(appKey);
}
<span style="color: rgb(0, 0, 255);">public</span> IQueryable<ImageSearchResult> SearchImages(<span style="color: rgb(0, 0, 255);">string</span> query) {
BingContext context = CreateContext();
<span style="color: rgb(0, 0, 255);">return</span> <span style="color: rgb(0, 0, 255);">from</span> i <span style="color: rgb(0, 0, 255);">in</span> context.Images.SafeResults()
<span style="color: rgb(0, 0, 255);">where</span> i.Query == query
<span style="color: rgb(0, 0, 255);">select</span> i;
}
<span style="color: rgb(0, 0, 255);">public</span> IQueryable<PageSearchResult> SearchPages(<span style="color: rgb(0, 0, 255);">string</span> query) {
BingContext context = CreateContext();
<span style="color: rgb(0, 0, 255);">return</span> <span style="color: rgb(0, 0, 255);">from</span> p <span style="color: rgb(0, 0, 255);">in</span> context.Pages.SafeResults()
<span style="color: rgb(0, 0, 255);">where</span> p.Query == query
<span style="color: rgb(0, 0, 255);">select</span> p;
}
}
Einige interessante Beabachtungen:
- SearchService leiten sich direkt vom DomainService ab. Es macht keinen Unterschied, ob man traditionelle Daten Abstraktions Schichten (DAL), wie LINQ to SQL oder Entity Framework verwendet. Das Domain Service Pattern beschreibt keine bestimmt DAL. Wir haben eine fundamentalen architektonischen Grundsatz, um nicht eine bestimmte Datenzugrifftechnologie zu erfinden.
- Man kann die BLinq API direkt vom DomainService heraus verwenden.
- Die Query-Methode gibt ein IQueryable eher als eine Liste von Resultaten zurück. Das erlaubt weitere Query-Kompositionen durch den Aufrufers. Die Skip/Take ermöglicht es dem Client Seitenwechsel durchzuführen, es wird über den Server verbunden und vom BingQueryProvider des Bing Service die letztendliche Query in eine URI übersetzt.
- Man kann natürlich auch zusätzlcihe Query Methoden einführen, welche zum Beispiel weitere Parameter übernehmen oder eine Serverseitige Caching ausführen oder Daten von mehereren Quellen von Daten verarbeiten oder die Daten vom Server filtern, usw. Die Werte des Domainservice erhält man und muss sie im Code plazieren. Man kann machen, was immer Sinn für die Applikation macht.
Wenn man das beachtet erhält man den folgend Code:
<span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">sealed</span> <span style="color: rgb(0, 0, 255);">partial</span> <span style="color: rgb(0, 0, 255);">class</span> ImageSearchResult : Entity {<br /> ...<br />}<br /><br /><span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">sealed</span> <span style="color: rgb(0, 0, 255);">partial</span> <span style="color: rgb(0, 0, 255);">class</span> PageSearchResult : Entity {<br /> ...<br />}<br /><br /><span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">sealed</span> <span style="color: rgb(0, 0, 255);">partial</span> <span style="color: rgb(0, 0, 255);">class</span> SearchContext : DomainContext {<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> SearchContext() : <br /> <span style="color: rgb(0, 0, 255);">this</span>(<span style="color: rgb(0, 0, 255);">new</span> HttpDomainClient(<span style="color: rgb(0, 0, 255);">new</span> Uri(<span style="color: rgb(0, 96, 128);">"DataService.axd/SearchApp-Services-SearchService/"</span>,<br /> System.UriKind.Relative))) {<br /> }<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> EntityList<ImageSearchResult> ImageSearchResults {<br /> get { ... }<br /> }<br /> <br /> <span style="color: rgb(0, 0, 255);">public</span> EntityList<PageSearchResult> PageSearchResults {<br /> get { ... }<br /> }<br /> <br /> <span style="color: rgb(0, 0, 255);">public</span> EntityQuery<ImageSearchResult> SearchImagesQuery(<span style="color: rgb(0, 0, 255);">string</span> query) {<br /> ...<br /> }<br /> <br /> <span style="color: rgb(0, 0, 255);">public</span> EntityQuery<PageSearchResult> SearchPagesQuery(<span style="color: rgb(0, 0, 255);">string</span> query) {<br /> ...<br /> }<br />}<br />
Man kann auch folgenden Code-Behind schreiben:
<span style="color: rgb(0, 0, 255);">private</span> <span style="color: rgb(0, 0, 255);">void</span> LoadData() {<br /> SearchContext context = <span style="color: rgb(0, 0, 255);">new</span> SearchContext();<br /> pagesList.ItemsSource = context.PageSearchResults;<br /><br /> EntityQuery<PageSearchResults> query =<br /> context.SearchPagesQuery(<span style="color: rgb(0, 96, 128);">"nikhil"</span>);<br /> context.Load(query);<br />}<br />
In der Applikation ist es sinnvoll die DomainDataSourc Control zu verwenden. Das deklarative Datenquellen Modell ist gut, wenn man nicht eine zusätzliche Logik auf der Präsentationschicht haben möchte. Es behandelt UI Parameterveränderungen, ladet die Daten asyncron, verwaltet das Laden der Daten usw.
<span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">TextBox</span> <span style="color: rgb(255, 0, 0);">Grid</span>.<span style="color: rgb(255, 0, 0);">Row</span><span style="color: rgb(0, 0, 255);">="2"</span> <span style="color: rgb(255, 0, 0);">x:Name</span><span style="color: rgb(0, 0, 255);">="searchTextBox"</span> <span style="color: rgb(0, 0, 255);">/></span><br /><br /><span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">ria:DomainDataSource</span> <span style="color: rgb(255, 0, 0);">x:Name</span><span style="color: rgb(0, 0, 255);">="pagesDataSource"</span><br /> <span style="color: rgb(255, 0, 0);">DomainContext</span><span style="color: rgb(0, 0, 255);">="{StaticResource searchContext}"</span><br /> <span style="color: rgb(255, 0, 0);">QueryName</span><span style="color: rgb(0, 0, 255);">="SearchPagesQuery"</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">ria:DomainDataSource.QueryParameters</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">riadata:ControlParameter</span> <span style="color: rgb(255, 0, 0);">ParameterName</span><span style="color: rgb(0, 0, 255);">="query"</span><br /> <span style="color: rgb(255, 0, 0);">ControlName</span><span style="color: rgb(0, 0, 255);">="searchTextBox"</span> <span style="color: rgb(255, 0, 0);">RefreshEventName</span><span style="color: rgb(0, 0, 255);">="TextChanged"</span> <span style="color: rgb(0, 0, 255);">/></span><br /> <span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">ria:DomainDataSource.QueryParameters</span><span style="color: rgb(0, 0, 255);">></span><br /><span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">ria:DomainDataSource</span><span style="color: rgb(0, 0, 255);">></span><br /><br /><span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">ItemsControl</span> <span style="color: rgb(255, 0, 0);">x:Name</span><span style="color: rgb(0, 0, 255);">="pagesList"</span> <span style="color: rgb(255, 0, 0);">ItemsSource</span><span style="color: rgb(0, 0, 255);">="{Binding ElementName=pagesDataSource, Path=Data}"</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">ItemsControl.ItemTemplate</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">DataTemplate</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">StackPanel</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">HyperlinkButton</span> <span style="color: rgb(255, 0, 0);">Content</span><span style="color: rgb(0, 0, 255);">="{Binding Title}"</span> <span style="color: rgb(255, 0, 0);">NavigateUri</span><span style="color: rgb(0, 0, 255);">="{Binding Uri}"</span> <span style="color: rgb(255, 0, 0);">TargetName</span><span style="color: rgb(0, 0, 255);">="_blank"</span> <span style="color: rgb(0, 0, 255);">/></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">TextBlock</span> <span style="color: rgb(255, 0, 0);">Text</span><span style="color: rgb(0, 0, 255);">="{Binding Description}"</span> <span style="color: rgb(0, 0, 255);">/></span><br /> <span style="color: rgb(0, 0, 255);"><</span><span style="color: rgb(128, 0, 0);">TextBlock</span> <span style="color: rgb(255, 0, 0);">Text</span><span style="color: rgb(0, 0, 255);">="{Binding DisplayUrl}"</span> <span style="color: rgb(0, 0, 255);">/></span><br /> <span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">StackPanel</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">DataTemplate</span><span style="color: rgb(0, 0, 255);">></span><br /> <span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">ItemsControl.ItemTemplate</span><span style="color: rgb(0, 0, 255);">></span><br /><span style="color: rgb(0, 0, 255);"></</span><span style="color: rgb(128, 0, 0);">ItemsControl</span><span style="color: rgb(0, 0, 255);"></span>
Jedes Mal wenn der Text in der Search TextBox sich ändert, formuliert die DataSource einen EntityQuery und gibt es an den SearchContext weiter, um die Ergebnisse zu laden. Die Ergebnisse werden an das ItemsControl abgegeben. Die meist identisch dem Codesnippet ist welcher auf imperative Variante geschrieben wird.

Das Diagramm zeigt die unterschiedlichen Schichten bei der Verwendung der Server-side proxing. Die grauen Boxen, z.B. das Web Service und das korrespondierende HttpDomainClient, sind Rohrleitungen. Die Webservice Box ist verantwortlich für die Freigabe der Applikationslogik während der DomainContext für das Bereitstellen der Daten durch das Arbeiten mit dem verbundenen DomainClient in der restlichen Applikation verantwortlich ist.
Zugriff auf Bing direkt vom Client
Es kann in vielen Fällen notwendig sein, dass ein Service nicht den Cross-Domain-Zugriff unterstützt oder dass das Service auf heikel Daten zugreift, welche nicht an den Client ausgeliefert werden soll. Bing ist auch ein sinnvoller Kanditat für den direkten Zugriff vom Client aus.
Während man beim serverseitigen Proxy die Anfragen an Bing direkt aus dem Silverlight Code gestartet wird verwendet man EntityList, EntityQuery und DomainContext-basierende Programmiermodelle am Client. Dafür ist es notwendig eine BingDomainContext Klasse der Silverlight Version von BLinq zu implementieren und einen BingDomainClient welcher die Logik für die Erstellung von Bing API Anfragen abkapselt.
<span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">class</span> SearchContext : BingDomainContext {<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> SearchContext()<br /> : <span style="color: rgb(0, 0, 255);">base</span>(appKey) {<br /> }<br />}<br />
BingDomainContext aus dem BLinq Framework hat folgende OM:
<span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">abstract</span> <span style="color: rgb(0, 0, 255);">class</span> BingDomainContext : DomainContext {<br /><br /> <span style="color: rgb(0, 0, 255);">protected</span> BingDomainContext(BingDomainClient domainClient)<br /> : <span style="color: rgb(0, 0, 255);">base</span>(domainClient) {<br /> }<br /><br /> <span style="color: rgb(0, 0, 255);">protected</span> BingDomainContext(<span style="color: rgb(0, 0, 255);">string</span> appKey)<br /> : <span style="color: rgb(0, 0, 255);">this</span>(<span style="color: rgb(0, 0, 255);">new</span> BingDomainClient(appKey)) {<br /> }<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> EntityList<ImageSearchResult> ImageSearchResults {<br /> get { ... }<br /> }<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> EntityList<PageSearchResult> PageSearchResults {<br /> get { ... }<br /> }<br /><br /> <span style="color: rgb(0, 0, 255);">public</span> EntityQuery<ImageSearchResult> SearchImagesQuery(<span style="color: rgb(0, 0, 255);">string</span> query) {<br /> ...<br /> }<br /><br /> <span style="color: rgb(0, 128, 0);">// Other image query methods...</span><br /><br /> <span style="color: rgb(0, 0, 255);">public</span> EntityQuery<PageSearchResult> SearchPagesQuery(<span style="color: rgb(0, 0, 255);">string</span> query) {<br /> ...<br /> }<br /><br /> <span style="color: rgb(0, 128, 0);">// Other page query methods...</span><br />}<br />
Beobachtungen:
- SearchContext wird von BingDomainContext abgeleitet, welches ein identisches Set von EntityLists und Query-Methoden wie vorhin in dem vorigen Ansatz.
- BingDomainContext verwendet den BingDomainClient anstelle des HttpDomainClient, der Defaultmässig angezeigt wird. BingDomainClient kapselt die Logik um mit Bing API zu arbeiten und fügt es unter die DomainContext. Das voher gezeigt Model ist intakt nur die Networkkommunikations Ebene wurde komplet weggelassen.
Die öffentliche Oberfläche und das Programmiermodell werden durch SearchContext beansprucht, welches BingDomainContext/BingDomainClient verwendet. Die restliche Applikation bleibt komplett unverändert. Die DataDomainSource weiß überhaupt nicht, dass die Network-Ebene verändert wurde. Die DataPager Controls verwenden die Skip/Take Expressions gleich wie zuvor und bekommt sie in Bing Such-URI übersetzt, dieses mal vom Client. Das folgende Korrespodenz-Diagramm zeigt wie der Client aufgebaut ist ohne eine serverseitigen Gegenstücke, typisch für .NET RIA Services Applikationen.

Es zeigt, wie einfach es ist Bing in eine Silverlight Applikation mit .NET RIA Services einzubinden, trotzdem mit der Ansteuerung unter der Verwendung Serverseitige Proxing oder direkten Clientzugriff. Das Framework und die Beispielapplikationen sind verfügbar. Zuvor sollte man aber die Bing Developer Seite besuchen und sich einen Applikationskey holen um einzufügen, damit der Code auch ausgeführt werden kann. Man braucht auch die aktuellste CTP des .NET RIA Services.
Einige technische Details des DomainClient…
Im Post von Nikhil Kothari’s “ViewModel + .NET RIA Services Part 2: Testability, Server Mocking and Dependencies” die Dehnbarkeit des DomainClient das Unit-testing clientseitig via MockDomainClient, welches eine In-Memory Speicherung eher als einen Aufruf eines Service, beschriebt. In diesem Post ist es wichtig, dass der DomainClient ein generelles Proxy für ein Service repräsentiert welches die API als Set von Query Operations, SumbitChanges Operationen und einem Set von Invoke Operationen abstraktiert.

Man kann vom DomainClient ableiten und es unterhalb eines DomainContext hinzufügen, um die Low-Level Details des Service zu übernehmen während das höhere-Level Programmiermodell intakt bleibt. Es ist zu empfehlen den Code zu überprüfen, wenn man sein eigenes Service oder back-end hinzufügen will.