Data Services on the Client
So far, we’ve seen how to expose a simple data model from our OData endpoint using WCF Data Services and we’ve used to browser to query over that data. Both of these are powerful, useful techniques but ultimately we’re also going to want to write clients that consume an OData endpoint, regardless of its source (you may recall from the beginning of this chapter that OData is served up from SQL Server Azure, SharePoint, WebSphere and Dallas as well as WCF Data Services).
Because OData is based on HTTP and XML, you can write use the .NET HttpWebRequest class and the various XML libraries built into .NET[11], but that’s quite a bit of work[12]. Instead, I prefer to use the tools built into Visual Studio 2010 to consume an OData endpoint.
Let’s keep things simple to start, so in a console application project, right-click on the name of the project in the Solution Explorer and choose Add Service Reference, which will bring up the Add Service Dialog shown in Figure 6.
Figure 6: Add Service Reference dialog
Notice in Figure 62 that we’ve entered the URL to the OData service example we’ve been playing with. After pressing Go, Visual Studio 2010 will dig out the service document to be able to populate the list of collections exposed by that service. In the Namespace box, you’re free to enter whatever you like, but I recommend something more useful than “ServiceReference1”.
Pressing OK will cause VS2010 to retrieve the metadata associated with the service and generate a file called “Reference.cs” (or “Reference.vb” depending on the language you’re using), which you can find under the service you generated in the Service References folder in the Solution Explorer, as shown in Figure 7[13]
Figure 7: An OData service reference inside a VS2010 project
What we’re getting here is a Data Services “proxy” class, so named because it serves as a proxy to the service that the client can use as if it were an object in its own address space. The job of the proxy is to form the HTTP requests like we’ve been doing in the browser and to parse the HTTP responses for ease of access by the client code. For example, our proxy exposes the Links and Users collections with corresponding .NET types that include the same properties that our being exposed by the OData endpoint. The fact that in this particular case, the endpoint also happens to be implemented in .NET is neither here nor there – this is the behavior the generated proxy class will exhibit based on the OData endpoint’s metadata, regardless of the implementation technique used by the server.
The top-level class generated by the Add Service Reference project item template is named after the namespace found in the metadata, e.g. TinyLinkContainer in our case. It derives from the DataServiceContext base class and represents the container for each collection found in the service document. Like the Entity Framework context we discussed in previous chapters, the Data Services context doesn’t maintain a constant connection between the client and the service. However, each client-side service context instance is going to keep track of the changes you make, like adding new entries, updating them, deleting them, etc. until you signal it’s time to push those changes to the service.
This makes usage pretty simple:
using System; using System.Linq; using AdminConsole.TinyLinkService; namespace AdminConsole { class Program { static void Main(string[] args) { var service = new TinyLinkContainer(new Uri(@"http://localhost:1675/TinyLinkService.svc")); var links = from link in service.Links.Expand("User") where link.IsActive orderby link.Url descending select link; foreach (var link in links) { Console.WriteLine("Url= {0}, IsActive= {1}, User.Name= {2}", link.Url, link.IsActive, link.User == null ? "<none>" : link.User.Name); } } } }
In this code, we create an instance of the service context we generated from the service metadata, build a LINQ query as if the data was in memory and execute it with the foreach statement. Notice the use of the Expand method on the Links collection, which maps to the $expand query option. Without this, the User property will be null. Data Services doesn’t have lazy loading on the client-side like EF does.
Also notice that the LINQ query we’ve formed sorts and filters the data in the same was as our most complicated OData query previously. It’s the job of Data Services on the client to take our LINQ query and turn it into an OData query. In fact, if you’d like to see the query that Data Services is generating, you can hang your cursor over the variable that holds that query (“links” in our case) and the data tip will show the URL used to retrieve the data from your query (as shown in Figure 8).
Figure 8: You can see the generated OData query URL while debugging in VS2010
You’ll notice from Figure 8 that our semantically equivalent LINQ query turns into the syntactically equivalent OData query.
Running this code produces the expected output (Figure 9).
Figure 9: Using the WCF Data Services client
And the Data Services client is not limited to simple query, as OData itself is not – both also support create, update and delete as well.
Footnotes
[11] I like LINQ to XML myself.
[12] You can read all about it in “Open Data Protocol by Example” at http://msdn.microsoft.com/en-us/library/ff478141.aspx (http://tinysells.com/143).
[13] You may have to press the Show All Files button at the top of the Solution Explorer to be allowed to look at your own files, but I won’t judge that particular user experience decision made by my corporate brothers and sisters.