LINQ to Bing, Silverlight and .NET RIA Services

This post demonstrates using BLinq or LINQ to Bing with .NET RIA Services in one of two ways: using .NET RIA Services end-to-end, and using BLinq within a DomainService, or using .NET RIA Services on the client (only) to access Bing directly from the client.

In my last post, I described BLinq, or LINQ to Bing, an API that allows you use LINQ to access the Bing search results (ok, so perhaps BLinq was not the best of names, given prior art on that name ... but anyway). I also alluded to .NET RIA Services integration, which I'll cover in this installment. In fact, IQueryable and the LINQ pattern lie at the very heart of .NET RIA Services, in allowing developers to access data in a consistent manner not just on client or server but across client and server, and enabling code to compose queries naturally. If you haven't read the intro post, please take the few minutes to check out the LINQ snippets to get a general sense before continuing on.

You might use Bing in your rich internet application in one of two ways: have your client make requests to Bing by proxying through your server, or make requests directly from the client, using a cross-domain networking API, such as the one provided by Silverlight. I'll show both approaches in this post.

Here is a screenshot of the Silverlight application using .NET RIA Services. It is pretty straightforward and minimalistic. It has a search TextBox, and a couple of panels to display matching pages and images, along with some paging UI. Behind the scenes are a couple of DomainDataSource controls to perform the data loading one page at a time.

Search Application screenshot

I have two apps with the same user interface demonstrating the two different approaches. The first uses RIA Services on both server and client. The second will use RIA Services only on the client, and will demonstrate how you can in fact use the client programming model and framework against a custom or very different back-end service (in this case, Bing).


Accessing Bing through a Server Proxy
I'll first cover the proxy-through-your-server solution. In this case, I'll use the LINQ to Bing API within a DomainService that runs on the server, and use the regular .NET RIA Services tooling to get a DomainContext generated into the client that I can program against from within my presentation tier. Here is my very basic DomainService class that encapsulates the search functionality needed in my simple application:

[EnableClientAccess]
public class SearchService : DomainService {

    private BingContext CreateContext() {
        string appKey = ConfigurationManager.AppSettings["BingAppKey"];
        return new BingContext(appKey);
    }

    public IQueryable<ImageSearchResult> SearchImages(string query) {
        BingContext context = CreateContext();
        return from i in context.Images.SafeResults()
               where i.Query == query
               select i;
    }

    public IQueryable<PageSearchResult> SearchPages(string query) {
        BingContext context = CreateContext();
        return from p in context.Pages.SafeResults()
               where p.Query == query
               select p;
    }
}

Some interesting observations:

  • SearchService derives directly from DomainService. It doesn't depend on your traditional DALs such as LINQ to SQL or Entity Framework. The domain service pattern doesn't prescribe a particular DAL, and in fact, we have a fundamental architectural tenet to not invent or require a particular data access technology.
  • Also, I can use the BLinq API as-is and directly from within my DomainService (just like I had in my console app in the previous post). No DAL abstractions are involved or required, and this is another fundamental architectural tenet.
  • The query methods return an IQueryable, rather than a list of results. This allows further query composition by the caller. Specifically it allows the client to pass in Skip/Take LINQ expressions that get remoted to the server and subsequently all the way to the Bing service when the BingQueryProvider translates the final query into a URI. This allows paging over the results. The value of this is as the developer of SearchService, I didn't have to code for every permutation of query parameters (which can become painful in more complex apps).
  • I can of course introduce additional query methods, that for example take additional parameters, or perform server-side caching or aggregation of data from multiple sources of data, or filtering of data on the server, log queries, save recent queries for the current user etc. etc. This is the value of domain services, and having a place to put your code. You can do what makes sense for your application. For the demo, these two simple ones that ensure strict results will suffice.

Based on this I get the following code generated on the client (look at SearchApp.Web.g.cs after clicking on Show All Files):

public sealed partial class ImageSearchResult : Entity {
    ...
}

public sealed partial class PageSearchResult : Entity {
    ...
}

public sealed partial class SearchContext : DomainContext {

    public SearchContext() : 
        this(new HttpDomainClient(new Uri("DataService.axd/SearchApp-Services-SearchService/",
                                          System.UriKind.Relative))) {
    }

    public EntityList<ImageSearchResult> ImageSearchResults {
        get { ... }
    }
        
    public EntityList<PageSearchResult> PageSearchResults {
        get { ... }
    }
        
    public EntityQuery<ImageSearchResult> SearchImagesQuery(string query) {
        ...
    }
        
    public EntityQuery<PageSearchResult> SearchPagesQuery(string query) {
        ...
    }
}

I can program against this programmatically. For example, I could write the following code in my code-behind:

private void LoadData() {
    SearchContext context = new SearchContext();
    pagesList.ItemsSource = context.PageSearchResults;

    EntityQuery<PageSearchResults> query =
        context.SearchPagesQuery("nikhil");
    context.Load(query);
}

In the application, I used a DomainDataSource control, rather than the code above. The declarative datasource model is nice when I don't have any additional logic to add on the presentation tier. I might as well let it handle UI parameter changes, loading data asynchronously, managing loaded data etc.

<TextBox Grid.Row="2" x:Name="searchTextBox" />

<ria:DomainDataSource x:Name="pagesDataSource"
  DomainContext="{StaticResource searchContext}"
  QueryName="SearchPagesQuery">
  <ria:DomainDataSource.QueryParameters>
    <riadata:ControlParameter ParameterName="query"
      ControlName="searchTextBox" RefreshEventName="TextChanged" />
  </ria:DomainDataSource.QueryParameters>
</ria:DomainDataSource>

<ItemsControl x:Name="pagesList" ItemsSource="{Binding ElementName=pagesDataSource, Path=Data}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <HyperlinkButton Content="{Binding Title}" NavigateUri="{Binding Uri}" TargetName="_blank" />
        <TextBlock Text="{Binding Description}" />
        <TextBlock Text="{Binding DisplayUrl}" />
      </StackPanel>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Each time the text in the search TextBox changes, the DataSource formulates an EntityQuery and passes it on to its SearchContext to load results. These results get rendered in the bound ItemsControl. This is almost identical to the snippet of code I wrote in the imperative variant I started with.

Search App using Server-side Proxying

Above is the diagram of the different layers when using server-side proxying. The gray boxes, i.e. the web service and the corresponding HttpDomainClient are plumbing. The web service box is responsible for exposing your application logic, while the DomainContext is responsible for surfacing your data to the rest of the application by working against the associated DomainClient. It will be interesting to compare this with the corresponding diagram for direct access to Bing from the client, which follows next.


Accessing Bing directly from the Client
As you probably observed in the code above, the server doesn't do anything particularly interesting, other than proxying requests. In many cases, a proxy is necessary, when a service doesn't support cross-domain access, or you've got sensitive information to access the service that you don't want to ship to the client. Bing however is a reasonable candidate for accessing directly from the client.

While I want let go of my server-side proxy, and make requests to Bing directly from my Silverlight code, I'd still like to use the EntityList, EntityQuery and DomainContext-based programming model on the client. To make this possible, I've implemented a BingDomainContext class the Silverlight version of BLinq, and a BingDomainClient which encapsulates logic for making Bing API requests. Your Silverlight application can derive from that instead of using generated code based on some server-side code.

public class SearchContext : BingDomainContext {

    public SearchContext()
        : base(appKey) {
    }
}

BingDomainContext from the BLinq framework has the following OM:

public abstract class BingDomainContext : DomainContext {

    protected BingDomainContext(BingDomainClient domainClient)
        : base(domainClient) {
    }

    protected BingDomainContext(string appKey)
        : this(new BingDomainClient(appKey)) {
    }

    public EntityList<ImageSearchResult> ImageSearchResults {
        get { ... }
    }

    public EntityList<PageSearchResult> PageSearchResults {
        get { ... }
    }

    public EntityQuery<ImageSearchResult> SearchImagesQuery(string query) {
        ...
    }

    // Other image query methods...

    public EntityQuery<PageSearchResult> SearchPagesQuery(string query) {
        ...
    }

    // Other page query methods...
}

Interesting observations:

  • SearchContext derives from BingDomainContext, which provides identical set of EntityLists and query methods as we saw before in the first approach.
  • BingDomainContext uses a BingDomainClient instead of the regular HttpDomainClient you get by default as shown in bold. This is the key. BingDomainClient encapsulates the logic to work with the Bing APIs and plugs in under the DomainContext. So the programming model on top remains intact, but the network communication layer has been completely swapped.

The public surface area, and programming model exposed by SearchContext that uses BingDomainContext/BingDomainClient is 100% identical. The rest of the application remains completely unchanged. For example, DomainDataSource doesn't know anything about the fact that the networking layer has changed. The DataPager controls compose Skip/Take expressions into the LINQ query as before, and it all gets translated into Bing search URIs, this time on the client, rather than on the server. Here is the corresponding diagram, that shows how the client exists without any server-side counterparts of typical .NET RIA Services applications:

Search App using Direct Access to Bing

Hopefully this shows how easy it is to incorporate Bing into your Silverlight applications using .NET RIA Services, regardless of which approach, using server-side proxying or direct client access, makes sense for your application. All of the framework code and both sample applications are available for download. As before you'll need to visit the Bing developer page, and retrieve an application key to use on your end when running the code. You'll also need the latest [http://code.msdn.microsoft.com/riaservices] CTP of .NET RIA Services.


Some technical details on DomainClient...
I had previously described the DomainClient extensibility point in my post on unit testing client-side code by mocking out the server via a MockDomainClient that uses an in-memory store rather than making requests to a service. Essentially as I described in that post, the DomainClient represents a general proxy for services whose API can be abstracted as a set of Query operations, a SubmitChanges operation, and a set of Invoke operations.

DomainContext and DomainClient

You can derive from DomainClient and plug it in underneath a DomainContext to take over the low-level details of working against a service, while keeping the higher level programming model intact. I'd recommend checking out the code if you're interested in plugging in your own service or back-end. If you are doing this or interested in this, I'd love to hear about it! I am personally interested in writing a TwitterDomainClient...

From a layering perspective, here are the different bits within the .NET RIA Services client framework and their purposes:

  • Entity, EntityList, EntityContainer - these represent the client-side data model. You've got your “record” instance, a list of instances, and a set of lists. Roughly speaking these map to DataRow, DataTable and DataSet if you're familiar with those. In fact, they can be used independently as a subsystem that allows you to work with and manage a set of objects, potentially related via associations, tracked for changes, and identity managed.
  • DomainContext - the programming model that provides a wrapper over the Entity subsystem to provide semantics around loading of data via queries, and submitting of changes in the form of a change set.
  • DomainClient - the layer that handles serialization, protocol and the specific service to communicate with. It translates the abstract notion of query, submit and invoke to the specific APIs exposed by some service.

Hopefully this shows how the .NET RIA Services client framework is flexible and makes its base services and programming model applicable in a variety of contexts and services, even though we've specifically done a bunch of work to make the end-to-end scenario involving your client talking to its own server half super simple.

Posted on Wednesday, 8/26/2009 @ 11:20 AM | #Silverlight


Comments

9 comments have been posted.

Dan

Posted on 8/26/2009 @ 12:41 PM
In designing a RIA Services application, do you think that many LOB applications will have multiple Domain Context/Client sets? I'm considering translating one of my existing web applications to a Silverlight application, and putting all of the logic for all of the service calls together into one Domain Context/Client would result in a huge, difficult to organize class. Forgive my lack of imagination, it's just that all of the example applications I've seen seem to use a single Context/Client. If one were to use multiple domains, would the client-side entities be reused between domains provided they are the same shape on the server side?

Nikhil Kothari

Posted on 8/26/2009 @ 4:22 PM
@Dan
You can certainly have multiple DomainContexts on the client... multiple instances of the same type, as well as multiple types.

Think of a DomainContext type as mapping to a DomainService (when you have client/server in the same logical app). Each domain service then encapsulates a logical set of functionality associated with a "domain". And an app can have multiple logical functions. Hence multiple domain services and correspondingly multiple domain contexts.

Also think of a particular DomainContext instance as a unit of work. You load some data, do some work, and then submit it as a single changeset to the server.

Hope that helps!

Rich Griffin

Posted on 8/27/2009 @ 12:57 AM
Hi nikhi,

nice post dude! I would be very interested in building a Twitter version as you mentioned in your post there is a Twitter to linq client that I saw recently and I am interested to se if this can be reused in this context. Once this is built it would be cool to see how Twitter and bing play together so that it would be possible to use text from Twitter as the search parameters for bing.

I am also interested in how this works with an MVVM using Unity or Ninject and a service locator pattern

senthilnathan

Posted on 8/27/2009 @ 1:15 PM
nice post! can you share the application source code?

Nikhil Kothari

Posted on 8/27/2009 @ 3:58 PM
@senthilnathan - click the download link ... it includes source for both framework and applications.

@Rich - if you click the link on unit testing you'll see how you can use the same DomainClient mechanism to mock the server when testing view models.

viktor

Posted on 10/5/2009 @ 12:37 AM
RIA Services + POCO ???

it seems as if value-object is not supported yet!(:
i.e:
public class Person
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual Address Address { get; set; }
}

public class Address
{
public int Number { get; set; }
public string Street { get; set; }
public string PostCode { get; set; }
}

or

var person = new Person();
person.Address = new Address(){Street="unknown",Number=0,PosCode="AX"};

do you know maybe any workaround?
thanks in advance!
viktor

suleman

Posted on 12/17/2009 @ 12:51 AM
Hi nikhilk,
pagesList.ItemsSource = context.PageSearchResults;

EntityQuery<PageSearchResults> query = context.SearchPagesQuery("nikhil");
context.Load(query);
this works fine and it binds to the grid.
However , if i try to use context.PageSearchResults in a variable , i can't do so , Even its count is 0.
Can you please tell me , how can i do it so that i could do my custom processing.

PS : this problem exists with any datasource using RIA and EDM
Thanks and Best Regards,
Suleman

suleman

Posted on 12/17/2009 @ 1:05 AM
Hi nikhilk,
pagesList.ItemsSource = context.PageSearchResults;

EntityQuery<PageSearchResults> query = context.SearchPagesQuery("nikhil");
context.Load(query);
this works fine and it binds to the grid.
However , if i try to use context.PageSearchResults in a variable , i can't do so , Even its count is 0.
Can you please tell me , how can i do it so that i could do my custom processing.

PS : this problem exists with any datasource using RIA and EDM
Thanks and Best Regards,
Suleman

Adam

Posted on 2/15/2010 @ 3:28 PM
Hi Nikhilk,

Dan asked "If one were to use multiple domains, would the client-side entities be reused between domains provided they are the same shape on the server side?" and you replied (perhaps not to that question) "yes". But when I try to reuse classes across multiple domain contexts I get an error telling me that Entity types can not be shared across mutliple domain services.

This is a problem for me at the moment because I have cross cutting classes such as UnitOfMeasure which appears on many of my objects across several domains. It would be good if I could share the type across multiple domains.

Is there a design reason behind this and if so, what design changes or patterns do I need to implement to make this work?

Regards,
Adam
Post your comment and continue the discussion.