Watch recordings of Brian's WCF RIA Services webinars:
This article is the tenth and final part of the series WCF RIA Services.
Introduction
As mentioned in Part 1 of this article series, WCF RIA Services only supports code generating the client proxy code in Silverlight projects. However, that does not mean you cannot call your domain services from other clients. If I were not going to have a Silverlight client application as my primary client application, I would not bother defining my services as domain services. I would instead define normal WCF services or possibly WCF Data Services. To me, most of the benefit of WCF RIA Services is in the code generated client proxy code and client side framework. The validation support, the security model, the service error handling, and the deferred query execution are the things I think are most compelling about using RIA Services.
But If I do have a Silverlight client and use RIA Services, I probably don't want to have to implement a separate set of services for my non-Silverlight clients. The good news is, you don't have to. It is easy to expose additional endpoints from your domain services that can be consumed by other clients. In this article, I'll show you how to enable those endpoints, and will show what is involved in consuming your RIA domain services from non-Silverlight clients. Your options include exposing a read-only OData service endpoint, a full functionality SOAP endpoint compatible with the basicHttpBinding in WCF, or a full functionality REST JSON encoded endpoint.
You can download the source code for this article here.
Exposing an OData Endpoint From Your Domain Service
OData is short for the Open Data Protocol. It is a REST-based web service protocol for exposing data for querying via web services, and optionally allowing updates via that web service as well. You can read up on OData at http://www.odata.org/. OData uses the ATOM protocol for encoding the data in the HTTP body of the REST messages that flow from and to your service. OData allows you to express a complex query through parameters in the URL that is used to address the service. OData supports a subset of the common LINQ query operations such as filtering (the Where operation in LINQ), projection (the Select operation in LINQ), and paging (Take and Skip operations in LINQ). Additionally, the OData protocol allows you to send inserts, updates, and deletes for an exposed entity collection if the service allows it.
RIA Services allows you to expose a query-only OData endpoint (no updates) from your domain services. The exposed feed only allows you to retrieve the entire collection exposed by a query method. You cannot pass query filters or paging operations down to the service through the OData protocol, so the functionality is fairly limited at the current time. In a future release of WCF RIA Services they will probably support updates and more complex query operations, but for now you can basically just call your domain service query methods and return the collection that the server method returns without being able to filter from the client side.
This capability is part of the core WCF RIA Services libraries. All you need to do is remember to check the box when you first create your domain service as shown in the following figure.
If you forgot to do that when you created the domain service, don’t fret. You can always add a new domain service to the project and check the box for that domain service, and then delete that domain service. Checking the box adds a sectionGroup to your config file, adds a new domainServices section to the system.serviceModel part of your config file, and adds another reference to the project to the System.ServiceModel.DomainServices.Hosting.OData.dll library. Additionally, the [Query] attribute is added to each of the entity query methods added by the wizard. If you forget to check the box, you will need to add those attributes yourself as well.
[Query(IsDefault=true)]
public IQueryable<Task> GetTasks()
{ ... }
[Query(IsDefault = true)]
public IQueryable<TimeEntry> GetTimeEntries()
{ ... }
[Query(IsDefault = true)]
public IQueryable<Customer> GetCustomers()
{ ... }
The sectionGroup it adds to the config file looks like this:
<system.serviceModel>
<domainServices>
<endpoints>
<add name="OData"
type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory,
System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
...
</endpoints>
</domainServices>
...
</system.serviceModel>
This endpoint does have metadata turned on, so clients can easily generate client proxy code from the endpoint like they would from any other WCF service. The address that this endpoint is exposed on is just the base domain service address (hosted server address + fully qualified domain service class name with dots replaced by dashes + .svc) with /odata appended to it.
Consuming the OData Endpoint From a .NET Client
What if you want to consume that OData feed? There are a variety of tools out there that can consume OData feeds, including the OData Explorer, a plug in for Excel, and other tools. If you want to consume that data by querying it via services from another .NET client, it is very easy because Visual Studio can generate a client proxy for you in any .NET project. There is also a command line tool called DataSvcUtil.exe that can do the same client code generation, making it easy to consume the feed.
To demonstrate this, I can add a WPF Application project to the solution. I then select Add Service Reference from that project and enter the address to the odata feed:
http://localhost:11557/TaskManager-Web-TasksDomainService.svc/odata
You should see that the service is found, and you will see a collection set for each of your entities that you have the [Query(IsDefault=true)] attribute on:
After you click OK, a client proxy will be generated. The generated OData proxy is quite different than a normal WCF service proxy. Instead of exposing methods that you call on the service, it exposes the entity sets. With normal OData services, you can form LINQ queries on those entity sets, and when you iterate over that expression (or call ToList or Count or other LINQ operators that do not defer execution), they will actually execute on the server side. The OData proxy sends the query expression tree to the server in a similar way to how WCF RIA Services does, by forming a query string from the expression tree of the LINQ expression as I explained in Part 3.
Unfortunately, the RIA Services implementation only allows you to ask for the entire entity set via OData. If you try to use other LINQ expressions such as Where or Take, you will get an error back that says “Query options are not allowed.” However, you can use the OData feed to retrieve all the entities exposed by a domain service query method.
For example, I could execute the following code in the WPF client:
Uri serviceUri = new Uri("http://localhost:58041/TaskManager-Web-TasksDomainService.svc/odata");
TasksDomainService proxy = new TasksDomainService(serviceUri);
List<Task> tasksBefore2Jun = proxy.TaskSet.ToList();
This would return the whole list of Tasks from the server, and then the client could present that data. However, if it allowed the user to edit the data, there would be no way to send the changes back to the server via the OData endpoint. Notice that the way the proxy works is by exposing entity sets as a property on the proxy itself. Also notice that the proxy requires a URL to the service on construction. There is no contract or binding associated with an OData proxy and you pass the address in through the constructor, so there is no need for client configuration of the proxy either.
Exposing a SOAP Endpoint From Your Domain Service
If you want to be able to execute your query and update methods from other clients, then you can use the SOAP or JSON endpoints that can also be enabled on your domain service. These require that you download and install the RIA Services Toolkit in addition to having the core RIA Services functionality that you get through the Silverlight 4 Tools for Visual Studio 2010.
The SOAP endpoint is a WCF basicHttpBinding compatible endpoint that can be easily consumed by just about any platform that speaks SOAP. To add the SOAP endpoint, you just add another endpoint in the domainServices section in your config, in the same place as the OData endpoint shown earlier. It looks like this:
<system.serviceModel>
<domainServices>
<endpoints>
<add name="soap"
type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory,
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
...
</endpoints>
</domainServices>
...
</system.serviceModel>
You will also need to add a reference in the web host to Microsoft.ServiceModel.DomainServices.Host, which is where the SoapXmlEndpointFactory type is defined as you can see from the config code above.
That endpoint does have metadata turned on, so clients can easily generate client proxy code from the endpoint like they would from any other WCF service. The address that this endpoint is exposed on is just the base domain service address with /soap appended to it.
Consuming the SOAP Endpoint From a .NET Client
To consume the SOAP Endpoint, you just do a normal Add Service Reference in the client project, or use svcutil.exe, or hand-code a proxy class using the ClientBase<T> base class. Using Add Service Reference is the easiest if you are new to WCF Services.
To add a service reference to the SOAP endpoint, just point Add Service Reference or svcutil.exe to the default address of your domain service, http://localhost:58041/TaskManager-Web-TasksDomainService.svc for the sample application. That will generate the compatible proxy and configuration code for the client.
Then you could write client code like the following to retrieve the Tasks collection and make an update to one of the tasks and send it back to the service to persist the change:
TasksDomainServicesoapClient proxy = new TasksDomainServicesoapClient();
// Retrieve the full collection,
// no ability to filter server side unless additional methods exposed
QueryResultOfTask result = proxy.GetTasks();
Task[] tasks = result.RootResults; // Extract the real collection from the wrapper
// Make a modification
tasks[0].TaskName = "Modified by SOAP Client";
// Wrap it in ChangeSetEntry
ChangeSetEntry changeEntry = new ChangeSetEntry();
changeEntry.Entity = tasks[0];
changeEntry.Operation = DomainOperation.Update;
// Send the changes back to the server as an array of ChangeSetEntries
proxy.SubmitChanges(new ChangeSetEntry[] { changeEntry });
Task[] newFetchTasks = proxy.GetTasks().RootResults;
proxy.Close();
Most of the complexity in dealing with the SOAP endpoint is in wrapping up changes in the ChangeSetEntries. That type supports sending the original entity and the modified entry back as well for optimistic concurrency checking or so that the server side can optimize the query by knowing which properties have actually changed on the object. Other than the wrapping of the entities, this is just normal WCF proxy-based service calls.
In the sample code for this article, I turned off security to keep things focused on the basic mechanisms of exposing the endpoints and calling them. To secure the endpoints, you would again just leave the [RequiresAuthentication] attribute on the domain service and add an AuthenticationDomainService as discussed in Part 7. On the client side, you would need to make a call to the authentication domain service first to establish a login session. You would also need to enable a cookie container on the proxy for both the authentication domain service endpoint and the other domain services you want to call. Finally, you would need to copy the cookie container from the authentication service proxy to the other proxies after logging in. For a great walkthrough on this in the context of a Windows Phone 7 client, see this blog post by Marcel de Vries.
Exposing a REST/JSON Endpoint
Exposing a REST/JSON style endpoint from your domain service that functions just like the SOAP one just described, it is just another endpoint declaration in your configuration file.
<add name="JSON"
type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory,
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
/>
You can then use your favorite approach such as a WebClient or HttpWebRequest in .NET to issue the HTTP request to the REST endpoint, and can use something like the WCF DataContractJsonSerializer to decode and encode the JSON payload in the HTTP body of the message. The address scheme is based on the addressing scheme of WCF service methods that you expose via REST. For example, to call the GetTasks method, you would just address http://localhost:58041/TaskManager-Web-TasksDomainService.svc/json/GetTasks.
Summary
As you can see, it is a fairly simple matter to expose the additional endpoints for OData, SOAP, and REST/JSON from your domain services. Because of the limitations on the OData endpoint in the current release, I find that one to be the least useful. However, the SOAP and REST endpoints do make it fairly easy to consume your domain services from other platforms. If I needed to provide CRUD services for a set of entities and needed to write client applications on multiple platforms with the full set of functionality, and wanted to make it as easy as possible for others to write clients for my services, I would not use WCF Data Services for that. I would use either WCF Data Services to expose a fully functional OData endpoint, or I would write normal WCF Services where I was not constrained by the server side model of WCF Data Services. However, if I was writing a complex Silverlight application that was the primary client application, and just wanted to be able to expose some of the same entity CRUD functionality to other clients without needing to write separate services for them or give up the client side benefits of WCF RIA Services for my Silverlight client, then these additional endpoints are just what is needed.
So that brings me to the end of this series on WCF Data Services. And I happen to be writing this on New Years Eve (day) of 2010, so it happens to also be the end of a year and end of a decade as well. Keep an eye on my blog at http://briannoyes.net/ for additional posts about WCF RIA Services, and I will probably write some other articles on this and other topics here on The SilverlightShow as well. Thanks for reading, and please let me know any feedback you have through the comments.
You can download the source code for this article here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on twitter @briannoyes.