Service Operations in ADO.NET Data Services
One way in which you can expose additional resources from your ADO.NET Data Service is to implement "service operations" on your WebDataService subclass.
For example, let's say we want to return all customers in a given city in a pre-baked entry point. We can write this code on the server:
public class WebDataService1 : WebDataService< Model.Entities >
{
public static void InitializeService(IWebDataServiceConfiguration config)
{
config.SetResourceContainerAccessRule("Customers", ResourceContainerRights.AllRead);
config.SetServiceOperationAccessRule("CustomersInLondon", ServiceOperationRights.All);
}
[WebGet]
public IQueryable<Model.Customers> CustomersInLondon()
{
return from c in this.CurrentDataSource.Customers
where c.City == "London"
select c;
}
}
Here's what's going on:
- In InitializeService, we make sure that the resource set we have is visible.
- Next, we make sure that the service operation we have is also visible.
- We declare a method, CustomersInLondon, which returns an IQueryable of customers.
- We add an attribute to the method, WebGet, which indicates we want to allow GET operations on this method.
- Finally, we return the query we're interested in. Now that there is a property CurrentDataSource, of type Model.Entities (the 'T' in WebDataService<T>), which we can use as the querying context.
Now we can hit F5 and run our project, and navigate to the following URL to get customers from London.
https://localhost/WebDataService1.svc/CustomersInLondon
Also, if we look at metadata on https://localhost/WebDataService1.svc/$metadata, we'll see the following bit of CSDL:
<FunctionImport Name="CustomersInLondon" EntitySet="Customers" ReturnType="Collection(Model.Customers)" />
Note that because we're returning an IQueryable, we can keep composing over the returned results, for example by filtering them:
https://localhost/WebDataService1.svc/CustomersInLondon?$filter=ContactTitle%20eq%20'Sales%20Manager'
Or navigating through them:
https://localhost/WebDataService1.svc/CustomersInLondon('AROUT')/Orders
Let's say that we didn't want to allow clients to do this - instead, we want to return a very "locked down" set of results. In that case, we simply need to change our method to return an IEnumerable result rather than an IQueryable. Note that even if the actual type is an IQueryable, as long as the result type is declared as an IEnumerable, the server won't allow composition over it.
[WebGet]
public IEnumerable<Model.Customers> CustomersInLondon()
{
return from c in this.CurrentDataSource.Customers
where c.City == "London"
select c;
}
So this URL still returns results as expected: https://localhost/WebDataService1.svc/CustomersInLondon
But this fails with a message 'Query options $expand, $filter, $orderby, $skip and $top cannot be applied to the requested resource.': https://localhost/WebDataService1.svc/CustomersInLondon?$filter=ContactTitle%20eq%20'Sales%20Manager'
Let's say that now want to allow the customers to tell us which specific city they're interested in, rather than hard-coding the city name.
We can add the following method to our class to enable this.
...
public static void InitializeService(IWebDataServiceConfiguration config)
{
...
config.SetServiceOperationAccessRule("CustomersInCity", ServiceOperationRights.All);
}
[WebGet]
public IQueryable<Model.Customers> CustomersInCity(string cityName)
{
return from c in this.CurrentDataSource.Customers
where c.City == cityName
select c;
}
Now we can get results for this operation with the following URL: https://localhost/WebDataService1.svc/CustomersInCity?cityName='London'
As you can see, the syntax is simply the parameter name, an equals, and the value literal as you would have used in a filter or a key. The only parameter types currently supported are primitive types (numbers, string, DateTime, byte array, Guid). This is in following the the form encoding used by web browser for FORM tags.
If you want to pass parameters in the request body rather than the query portion of the URL, you can use WebInvoke on the service operation method instead of WebGet. The clients then use POST rather than GET, and pass the queries in the body of the request, using application/x-www-form-urlencoded encoding.
There are two more attributes that are interesting for service operations. You can use the Microsoft.Data.Web.SingleResultAttribute attribute to indicate a single result will be returned from a specific operation.
...
public static void InitializeService(IWebDataServiceConfiguration config)
{
...
config.SetServiceOperationAccessRule("LastOrderDate", ServiceOperationRights.All);
}
[SingleResult]
[WebGet]
public IQueryable<DateTime> LastOrderDate()
{
var result =
from o in this.CurrentDataSource.Orders
orderby o.OrderDate
select o;
return result.Take(1).Select((o) => o.OrderDate.Value);
}
You can now access this value from the following URL: https://localhost/WebDataService1.svc/LastOrderDate
And finally, the MimeTypeAttribute can be returned to indicate that a given MIME type applies to the returned value of an operation.
...
public static void InitializeService(IWebDataServiceConfiguration config)
{
...
config.SetServiceOperationAccessRule("WelcomePage", ServiceOperationRights.All);
}
[MimeType("text/html")]
[SingleResult]
[WebGet]
public IQueryable<string> WelcomePage()
{
return new string[]
{
"<html><head><title>Welcome</title></head>" +
"<body><h1>Welcome!</h1><p>Currently we have " +
this.CurrentDataSource.Customers.Count().ToString() +
" customers.</p></body></html>"
}.AsQueryable();
}
You can now access this information through the following URL: https://localhost/WebDataService1.svc/WelcomePage/$value
Now, there are far better methods for generating web pages, but I just wanted to give a little taste of how having a (simple!) uniform interface for accessing resources allows you to integrate disparate systems, like a web browser and an ADO.NET Data Service.
This post is part of the transparent design exercise in the Astoria Team. To understand how it works and how your feedback will be used please look at this post.
Comments
Anonymous
January 21, 2008
PingBack from http://msdnrss.thecoderblogs.com/2008/01/22/service-operations-in-adonet-data-services/Anonymous
January 22, 2008
It appears to me that SingleResult] [WebGet] public IQueryable<DateTime> LastOrderDate() { var result = from o in this.CurrentDataSource.Orders orderby o.OrderDate select o; return result.Take(1).Select((o) => o.OrderDate.Value); } Will return the FirstOrderDate. Isn't "descending" missing? --rjAnonymous
January 22, 2008
The comment has been removedAnonymous
January 25, 2008
Speaking of service operations , Mike Taulty has a screen cast on them here - much nicer than my dryAnonymous
January 29, 2008
Exception handling can be tricky in a distributed system, and I'd like to use this post to show a coupleAnonymous
January 30, 2008
Great post! - Thanks for these samples. Got me up to speed in less than 30 minutes.Anonymous
July 13, 2008
Today's entry is about a feature that allows ADO.NET Data Services to play very nicely with other softwareAnonymous
October 16, 2008
I have been working lately with ADO.NET Data Services, and I found several tutorials on how to create...Anonymous
October 28, 2008
ADO.Net Data Services oltre ad esporre i metodi CRUD per lavorare con le entities, permette anche di