Ria Services under the hood Part 2 – Query Operations

In part 1 we looked into some of the plumbing under Ria Services, mostly around the serialization and service routing.  We used a “ServiceOperation” as the example in that case.  In this part, we’ll look into “Query” type operations under the hood.  We won’t rehash overlapping information, so if this information interests you, you might consider reading Part 1 if you have not done so already.  The source code for all of these posts can be found on my sky drive location.

Our sample will use this simple DomainService method:

[Query]
public IEnumerable<Person> SnagAllPeeps()
{
  return FakeDataAccessLayer.GetAllPeeps();
}

…consumed by our Silverlight client like so:



<
data:DataGrid x:Name="grdPeeps"KeyDown="grdPeeps_KeyDown"/>

private void CallQueryOperation(object sender, RoutedEventArgs e)
{
  this.grdPeeps.ItemsSource = _ds.Persons;
  _ds.Load(_ds.SnagAllPeepsQuery().Where(i=> i.Age>35).OrderBy(i=>i.Name),
    (LoadOperation<Person> loadOperation) =>
    {
      // Do whatever here.
    }, null);
}


While the flow under the hood for Query methods is similar to what we discovered when looking at ServiceOperation(s), with some exceptions around passing Linq query information.  The generated client-side DomainService (which is referred to as the DomainContext btw), contains a generated method that looks like this:

/// <summary>
/// Returns an EntityQuery for query operation 'SnagAllPeeps'.
/// </summary>
public EntityQuery<Person> SnagAllPeepsQuery()
{
    return base.CreateQuery<Person>("SnagAllPeeps", null, false, true);
}

 

You’ll note that a Systems.Windows.Ria.Data.EntityQuery<Person> is being returned by the base DomainContext.CreateQuery<T> method.  An EntityQuery object wraps up information about the query, including the System.Linq.IQueryable query.  In this case, we have a predefined query named “SnagAllPeeps”, with no [null] parameters, has No Side effects and IsComposable.  HasSideEffects, according to the documentation, will allow a Query operation to use an HTTP Post, while IsComposable determines whether the user can specify Linq operations on the client to apply on the server (hold that thought, you’ll see more about that in a second).   Lets see what this Http request looks like:

———————————————————————————————————————————————

GET /ClientBin/DataService.axd/SilverlightApplication1-Web-DomainService1/SnagAllPeeps?$where=(Age%3e35)&$orderby=Name HTTP/1.1

Accept: */*

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET CLR 1.1.4322; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; msn OptimizedIE8;ENUS)

Host: localhost.:53937

Connection: Keep-Alive

———————————————————————————————————————————————

You’ll notice that the EntityQuery where we restricted our age to be greater than 35 and ordering the persons by name was sent in the path of the HTTP Get (“?$where=(Age%3e35)&$orderby=Name“).  Pretty slick.  Let’s take a closer look.  Within the DomainContext.Load method some housekeeping is conducted and the call is handed off to the configured DomainClient, which is our good friend (see Part one) the HttpDomainClient, to the BeginQuery method.  The parameters and/or IQueryable query are serialized into a string in the HttpDomainClient.GetOperationData method.  If the method does not have side effects then an HTTP GET will be used and the operation data (parameters and serialized IQueryable) are packed onto the HTTP Url(which we can see in the HTTP Get dump above).  So, the call is made, and we’re off to the server…..

 

On the server-side, the same (see Part 1) DataServiceFactory we’ve seen is fired up and a DataService is retrieved and initialized.  The parameters are pulled out of the querystring for HTTP GET(s), or the Form collection for HTTP POST(s).  Internally a class of type DataServiceQueryRequest is used to invoke the request.  That class will orchestrate deserializing QueryParts from the HTTP request (if present), and constructs an IQueryable by rebuilding the Linq expressions on the server side (it appears that Ria Services supports “where”, “orderby”, “skip”, and “take” Linq operators currently).  This guy builds up a System.Web.DomainServices.QueryDescription object which packages up a pointer to the method, the [optional] parameters along with the [optional] IQueryable query and hands that off to the DomainService.Query method.  Inside this method, the target operation is executed, which in this case is our “SnagAllPeeps” method.  If an IQueryable is present, then it is applied to the result.  In our sample, the SnagAllPeeps method returns 4 results, but after our client-provided IQueryable passes over the wire (to filter by age), then only 2 results are returned from the server, which you can see below in the Http response:

———————————————————————————————————————————————

HTTP/1.1 200 OK

Server: ASP.NET Development Server/9.0.0.0

Date: Sun, 27 Sep 2009 16:19:25 GMT

X-AspNet-Version: 2.0.50727

Cache-Control: no-cache

Pragma: no-cache

Expires: -1

Content-Type: text/json; charset=utf-8

Content-Length: 430

Connection: Close

{"__type":"DataServiceResult:DomainServices","IsDomainServiceException":false,"Results":[{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web&quot;,"Id":"951b0038-be14-4338-bb81-f6fa68b315e9","Name":"Bob","Age":41},{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web&quot;,"Id":"4df1648d-eff8-433f-a2e4-2186ca24ba78","Name":"Joe","Age":38}],"TotalCount":-2,"ResultCount":2}

———————————————————————————————————————————————

You’ll see that in the default DomainService class generated by Visual Studio, there are //TODO tokens indicating the need to add additional queries with parameters.  Given what you just learned above, you’d want to refine the original query against a database in the DomainService query, rather than rely on client-side Linq query operators because the latter would retrieve all the rows and filter in the middle tier, whereas the former would allow relational databases to do what they do best (and most efficiently).

Summary

We’ve seen that Query operations are use much of the same plumbing we saw with service operations.  Additionally, Query operations bring Linq elements along with serializing IQueryable across the wire, and applying queries against the source retrieved from service operations.  In the next post, we’ll dig into the final operation type, the “Submit” operation, along with the C_UD portion of CRUD :)    Perhaps others will find this interesting.

Ria Services under the hood Part 1 – Service Operations

We’ve been using Ria Services at work for a LOB application written in Silverlight 3.  Ria Services is still pretty new, and when you use it, you find little quirks that don’t immediately make sense.  Some of the behavior prompted my curiosity, so I busted open Reflector and Fiddler to dig a little deeper, and thought I’d publish these findings for anyone who’s curious.  I’ll probably do this in bite-size chunks; hence the “Part #” post naming convention.  The focus is mainly on the serialization, marshalling, and service handling behaviors and does not address application services like security or the validation benefits of Ria Services.  To keep things simple, we use straight POCO and won’t muddy the waters by bringing in an ORM like the Entity Framework (which we are using at “The Company” with moderate success).  You can find all the code for this post (and other Ria Services posts) up on my space on sky drive here.   Just ignore the other pieces of code beyond the “EchoString” service operation for now.  Those other methods will be covered in future posts.

Here’s our simple service operation for our DomainService:

[ServiceOperation]
public string EchoString(string input)
{
  return input + “” + DateTime.Now.ToString();
}


And here is the Silverlight client consuming it:

    DomainService1 _ds = new DomainService1();

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      _ds.EchoString("jason calling",
        (InvokeOperation<string> invokeOperation) =>
      {
        MessageBox.Show(invokeOperation.Value);
      },null);
      
    }

When the client makes this call, what’s happening under the hood?  To answer that question, you can inspect the generated code that Ria Services places in your Silverlight client.  You can find that code by clicking the “Show all Files” option within Solution Explorer, and you’ll see a “Generated_Code” folder, containing the generated code projected from your DomainService in your web project.  There will be several classes in this file including DomainService1.  Check out the default constructor and you’ll notice this default constructor:

 /// <summary>
 /// Default constructor.
 /// </summary>
 public DomainService1() : 
         this(new HttpDomainClient(new Uri("DataService.axd/SilverlightApplication1-Web-DomainService1/", System.UriKind.Relative)))
 {
 }


The HttpDomainClient is what our client-side domain service uses to conduct the HTTP calls.  The server-side handler (DataService.axd) is registered in your website’s list of HttpHandlers in the web.config (see below), and that’s the guy receiving the HTTP calls (and ultimately getting those calls to your server side Domain Service object):



<
httpHandlers>

(others omitted for brevity)…

<addpath="DataService.axd" verb="GET,POST" type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"

  validate="false" />

</
httpHandlers>


Referring back to our “EchoString” Service Operation, here’s what’s going on in the generated client-side DomainService1 class (note that last call):

public InvokeOperation<string> EchoString(string input, Action<InvokeOperation<string>> callback, object userState)
{
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("input", input);
    return base.InvokeServiceOperation<string>("EchoString", parameters, callback, userState);
}

When we bust open the System.Windows.Ria.Data.DomainContext (which our DomainService1 derives from) in Reflector and look into that InvokeServiceOperation, we’ll note that [after validation work is conducted], a call to the DomainClient’s BeginInvoke method is called.  This passes through the DomainClient base, where some housekeeping is done, then onto the HttpDomainClient.BeginInvokeCore method.  Inside this method, a System.Net.HttpWebRequest object is created and setup for an HTTP Post (since this is a “ServiceOperation”), with content type set to “text/json”.  The parameters, callback info, and user state are all packed up and sent through as well.  So, now we’re heading to the server over the wire as json, so here’s what the request looks like through Fiddler:

———————————————————————————————————————————

POST /ClientBin/DataService.axd/SilverlightApplication1-Web-DomainService1/EchoString HTTP/1.1

Accept: */*

Content-Length: 17

Content-Type: text/json

Accept-Encoding: gzip, deflate

User-Agent: (omitted for brevity)

Host: localhost.:53937

Connection: Keep-Alive

Pragma: no-cache

["jason calling"]

———————————————————————————————————————————

Recall the “System.Web.Ria.DataServiceFactory”  assignment from the web.config entry containing the HttpHandler entry.  That guy splits out the portion of the HTTP path after the handler (the “SilverlightApplication1-Web-DomainService1” bit from the info above), replaces the dashes with dots, and uses reflection to snag an instance of that type (our server side DomainService1 guy).   A statically-cached map of the public instance service methods are created (or retrieved on subsequent requests) for the object, which will be used to execute the methods.  Methods are wrapped into classes that derive from DomainOperationEntry which provides additional information about the "Domain Operation” such as Authorization and the DomainOperation type (Query, Insert, Update, Delete, Custom, ServiceOperation, or Resolve).  The method name to execute is the 2nd part of the HTTP Path (the “EchoString” in this case).  Some checking is conducted to determine what type DomainOperationType (note the difference here of “DomainOperationType” which is a a more coarse categorization containing only three choices of “Query”, “ServiceOperation”, and “Submit”) will be executing.  Generally, HTTP “GET” is used for Query DomainOperationTypes whereas HTTP “POST” is used for ServiceOperation(s) and Submit.  Finally, a System.Web.Ria.DataService object is created, initialized with the DomainOperationEntry it will be working with, and is returned by the DataServiceFactory.  At this point, the DataService’s IHttpHandler.ProcessRequest(HttpContext) method is executed as part of the standard HttpPipeline in asp.net.  Method parameters are deserialized as json using the JavascriptSerializer.  Security checks are conducted to ensure the method is permitted, parameter types are resolved, validation is run [again] on the server, and the method is invoked on our DomainService1 object.  The response from our DomainService1 object is returned back out from the DomainService base class, and out through the DataService[.axd] serialized through the JavascriptSerializer (as json). 

For our example “EchoString” method, here is the raw HTTP response:

———————————————————————————————————————————

Server: ASP.NET Development Server/9.0.0.0

Date: Sat, 26 Sep 2009 21:18:21 GMT

X-AspNet-Version: 2.0.50727

Cache-Control: no-cache

Pragma: no-cache

Expires: -1

Content-Type: text/json; charset=utf-8

Content-Length: 128

Connection: Close

{"__type":"DataServiceResult:DomainServices","IsDomainServiceException":false,"ReturnValue":"jason calling 9/26/2009 5:18:21 PM"}

———————————————————————————————————————————

Summary

So, we’ve seen that Ria Services is handling a number of things for us behind the scenes, including serialization on the client, deserialization on the server, and routing the calls to the method counterparts on our DomainService object on the server.  These are things you can also do with other technologies such as WCF or plain old .asmx web services, but doing so requires creating those services, and maintaining service references to those in the client.  Ria services also does bring other goodness like the validation and application services like security to the table which is pretty sweet as well.  In the next post, we’ll look closer at Query Domain operations and see how those look under the hood and on the wire.