Accessing data as resource through URI - WCF Data Services

Open Data Protocol (OData for short) allows CRUD operations on your data by exposing it as a resource accessible through a URI. So you can try something like below directly on the browser to get a collection of all employees less than 26 years of age.

http://somesite.com/EmployeeService.svc/Employees?$filter=Age lt 26

You are now ‘accessing data as resources through URI’. And since OData uses the semantics of Representational State Transfer (REST), you can access these services from any client over standard HTTP protocol.

WCF Data Services allows us to create services by using OData protocol. Once an HTTP request is received by the WCF Data Service, the request gets deserialized into a format that the data-source can understand and execute. Although I will be using Entity Framework as the data model in this post, WCF Data Services works with any data structure as long as the an implementation of IQueryable interface is returned.

Let’s play with some code now. My Entity Framework data model (CameraEntities) looks like below:

image

I add a WCF Data Service called CameraService to my project.

image

The strange thing is that there are no changes made to the web.config file. My CameraService shows up as:

   1: public class CameraService : DataService< /* TODO: put your data source class name here */ >
   2: {
   3:     // This method is called only once to initialize service-wide policies.
   4:     public static void InitializeService(DataServiceConfiguration config)
   5:     {
   6:         // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
   7:         // Examples:
   8:         // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
   9:         // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
  10:         config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  11:     }
  12: }

As line 3 states, this method gets called only once, so it makes sense to add global settings here. The DataService takes an IQueryable type, in this case, the CameraEntities replacing in the TODO comment. The next thing is to provide access rules for the entities and your service operations (if any) so that they can be accessed as resources over the HTTP protocol. I have explicitly set the permissions on my types, although you can have blanket rules as seen in the comments.

   1: // This method is called only once to initialize service-wide policies.
   2: public static void InitializeService(DataServiceConfiguration config)
   3: {
   4:     // exposes all entities with AllRead rights;
   5:     //config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
   6:     // exposes all operations with AllRead rights;
   7:     //config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
   8:     config.SetEntitySetAccessRule("Cameras", EntitySetRights.AllRead);
   9:     config.SetEntitySetAccessRule("CameraTypes", EntitySetRights.AllRead);
  10:     config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  11:     config.UseVerboseErrors = true;
  12: }

If I run the service as is, I see that the two types, I have given access, are displayed.

image

I can also see the metadata information of the two types by appending the ‘$metadata’ keyword with the above query.

image

I have some data in the database tables already so I can do something like: http://localhost:59407/cameraservice.svc/Cameras and get a list of all entities in the Camera table (Just a note here, that only partial xml responses are shown below).

   1: <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
   2: <feed xml:base="http://localhost:59407/CameraService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
   3:     <title type="text">Cameras</title>
   4:     <id>http://localhost:59407/cameraservice.svc/Cameras</id>
   5:     <updated>2011-05-18T10:58:37Z</updated>
   6:     <link rel="self" title="Cameras" href="Cameras" />
   7:     <entry>
   8:         <id>http://localhost:59407/CameraService.svc/Cameras(1)</id>
   9:         <title type="text" />
  10:         <updated>2011-05-18T10:58:37Z</updated>
  11:         <author>
  12:             <name />
  13:         </author>
  14:         <link rel="edit" title="Camera" href="Cameras(1)" />
  15:         <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(1)/CameraType" />
  16:         <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  17:         <content type="application/xml">
  18:             <m:properties>
  19:                 <d:CameraId m:type="Edm.Int32">1</d:CameraId>
  20:                 <d:CameraTypeId m:type="Edm.Int32">3</d:CameraTypeId>
  21:                 <d:Description>Entry-level digital SLR</d:Description>
  22:                 <d:Price m:type="Edm.Double">40000</d:Price>
  23:                 <d:ManufacturerName>Canon</d:ManufacturerName>
  24:                 <d:MegaPixels m:type="Edm.Int16">10</d:MegaPixels>
  25:                 <d:ModelName>EOS DigitalRebel XTi</d:ModelName>
  26:             </m:properties>
  27:         </content>
  28:     </entry>
  29:     <entry>
  30:         <id>http://localhost:59407/CameraService.svc/Cameras(2)</id>
  31:         <title type="text" />
  32:         <updated>2011-05-18T10:58:37Z</updated>
  33:         <author>
  34:             <name />
  35:         </author>
  36:         <link rel="edit" title="Camera" href="Cameras(2)" />
  37:         <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(2)/CameraType" />
  38:         <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  39:         <content type="application/xml">
  40:             <m:properties>
  41:                 <d:CameraId m:type="Edm.Int32">2</d:CameraId>
  42:                 <d:CameraTypeId m:type="Edm.Int32">2</d:CameraTypeId>
  43:                 <d:Description>Pocket-friendly point-n-shoot</d:Description>
  44:                 <d:Price m:type="Edm.Double">12500</d:Price>
  45:                 <d:ManufacturerName>Nikon</d:ManufacturerName>
  46:                 <d:MegaPixels m:type="Edm.Int16">14</d:MegaPixels>
  47:                 <d:ModelName>S1100PJ</d:ModelName>
  48:             </m:properties>
  49:         </content>
  50:     </entry>
  51: </feed>

You see all the properties of a Camera type are returned back from the service. Just like a WCF Service, we can also define service operations in the WCF Data Service. I have an implementation of a service operation that gets back all the cameras by a camera type – Ultra-Compact, Compact or Digital SLR.

   1: public static readonly Func<CameraEntities, string, IQueryable<Camera>> GetCamerasByCameraTypeCompiled =
   2:     CompiledQuery.Compile<CameraEntities, string, IQueryable<Camera>>((cameraEntities, cameraTypeName)
   3:                           => from camera in cameraEntities.Cameras
   4:                              where camera.CameraType.Name == cameraTypeName
   5:                              select camera);
   6:  
   7: [WebGet]
   8: public IQueryable<Camera> GetCamerasByCameraType(string cameraTypeName)
   9: {
  10:     if(string.IsNullOrEmpty(cameraTypeName))
  11:     {
  12:         throw new ArgumentException("Argument cameraTypeName is invalid.");
  13:     }
  14:  
  15:     try
  16:     {
  17:         CameraEntities cameraEntities = CurrentDataSource;
  18:         // running compiled linq queries is more efficient
  19:         IQueryable<Camera> cameras = GetCamerasByCameraTypeCompiled(cameraEntities, cameraTypeName);
  20:  
  21:         return cameras;
  22:     }
  23:     catch (Exception exception)
  24:     {
  25:         throw new ApplicationException("An error occurred: {0}", exception);
  26:     }
  27: }

The GetCamerasByCameraType() service operation uses a compiled LINQ query (just to show off that I can write compiled LINQ queries he he!) to retrieve a collection of cameras. Before we run the query to test this, we need to set an access rule for this method. After adding

   1: config.SetServiceOperationAccessRule("GetCamerasByCameraType", ServiceOperationRights.AllRead);
   2: // alternatively you can use the '*' wildcard to set access to all service operations
   3: //config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);

to the InitializeService() static method, I can query http://localhost:59407/cameraservice.svc/GetCamerasByCameraType?cameraTypeName='Compact' in the browser’s address location.

   1: <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
   2: <feed xml:base="http://localhost:59407/CameraService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
   3:     <title type="text">GetCamerasByCameraType</title>
   4:     <id>http://localhost:59407/cameraservice.svc/GetCamerasByCameraType</id>
   5:     <updated>2011-05-18T13:02:09Z</updated>
   6:     <link rel="self" title="GetCamerasByCameraType" href="GetCamerasByCameraType" />
   7:     <entry>
   8:         <id>http://localhost:59407/CameraService.svc/Cameras(3)</id>
   9:         <title type="text" />
  10:         <updated>2011-05-18T13:02:09Z</updated>
  11:         <author>
  12:             <name />
  13:         </author>
  14:         <link rel="edit" title="Camera" href="Cameras(3)" />
  15:         <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(3)/CameraType" />
  16:         <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  17:         <content type="application/xml">
  18:             <m:properties>
  19:                 <d:CameraId m:type="Edm.Int32">3</d:CameraId>
  20:                 <d:CameraTypeId m:type="Edm.Int32">1</d:CameraTypeId>
  21:                 <d:Description>With 12x Optical Zoom, you can now shoot like a pro!</d:Description>
  22:                 <d:Price m:type="Edm.Double">8000</d:Price>
  23:                 <d:ManufacturerName>Canon</d:ManufacturerName>
  24:                 <d:MegaPixels m:type="Edm.Int16">12</d:MegaPixels>
  25:                 <d:ModelName>SX130 IS</d:ModelName>
  26:             </m:properties>
  27:         </content>
  28:     </entry>
  29:     <entry>
  30:         <id>http://localhost:59407/CameraService.svc/Cameras(4)</id>
  31:         <title type="text" />
  32:         <updated>2011-05-18T13:02:09Z</updated>
  33:         <author>
  34:             <name />
  35:         </author>
  36:         <link rel="edit" title="Camera" href="Cameras(4)" />
  37:         <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(4)/CameraType" />
  38:         <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  39:         <content type="application/xml">
  40:             <m:properties>
  41:                 <d:CameraId m:type="Edm.Int32">4</d:CameraId>
  42:                 <d:CameraTypeId m:type="Edm.Int32">1</d:CameraTypeId>
  43:                 <d:Description>Light on price - Heavy on features</d:Description>
  44:                 <d:Price m:type="Edm.Double">5000</d:Price>
  45:                 <d:ManufacturerName>Kodak</d:ManufacturerName>
  46:                 <d:MegaPixels m:type="Edm.Int16">12</d:MegaPixels>
  47:                 <d:ModelName>EasyShare ZX101</d:ModelName>
  48:             </m:properties>
  49:         </content>
  50:     </entry>
  51: </feed>

I can also get entities by their primary key by querying for something like: http://localhost:59407/cameraservice.svc/Cameras(1)

   1: <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
   2: <entry xml:base="http://localhost:59407/CameraService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
   3:     <id>http://localhost:59407/CameraService.svc/Cameras(1)</id>
   4:     <title type="text" />
   5:     <updated>2011-05-18T13:12:21Z</updated>
   6:     <author>
   7:         <name />
   8:     </author>
   9:     <link rel="edit" title="Camera" href="Cameras(1)" />
  10:     <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(1)/CameraType" />
  11:     <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  12:     <content type="application/xml">
  13:         - <m:properties>
  14:             <d:CameraId m:type="Edm.Int32">1</d:CameraId>
  15:             <d:CameraTypeId m:type="Edm.Int32">3</d:CameraTypeId>
  16:             <d:Description>Entry-level digital SLR</d:Description>
  17:             <d:Price m:type="Edm.Double">40000</d:Price>
  18:             <d:ManufacturerName>Canon</d:ManufacturerName>
  19:             <d:MegaPixels m:type="Edm.Int16">10</d:MegaPixels>
  20:             <d:ModelName>EOS DigitalRebel XTi</d:ModelName>
  21:         </m:properties>
  22:     </content>
  23: </entry>

Line 15 only shows the CameraTypeId, in order to include the CameraType entity, we need to instruct the query to do so: http://localhost:59407/cameraservice.svc/Cameras?$expand=CameraType. We now see the output contains the details of the CameraType entity for all Camera entities (lines 10-30, for example).

   1: <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
   2: <entry xml:base="http://localhost:59407/CameraService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
   3:     <id>http://localhost:59407/CameraService.svc/Cameras</id>
   4:     <title type="text" />
   5:     <updated>2011-05-18T13:25:25Z</updated>
   6:     <author>
   7:         <name />
   8:     </author>
   9:     <link rel="edit" title="Camera" href="Cameras(1)" />
  10:     <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CameraType" type="application/atom+xml;type=entry" title="CameraType" href="Cameras(1)/CameraType">
  11:         <m:inline>
  12:             <entry>
  13:                 <id>http://localhost:59407/CameraService.svc/CameraTypes(3)</id>
  14:                 <title type="text" />
  15:                 <updated>2011-05-18T13:25:25Z</updated>
  16:                 <author>
  17:                     <name />
  18:                 </author>
  19:                 <link rel="edit" title="CameraType" href="CameraTypes(3)" />
  20:                 <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Cameras" type="application/atom+xml;type=feed" title="Cameras" href="CameraTypes(3)/Cameras" />
  21:                 <category term="CameraDatabaseModel.CameraType" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  22:                 <content type="application/xml">
  23:                     <m:properties>
  24:                         <d:CameraTypeId m:type="Edm.Int32">3</d:CameraTypeId>
  25:                         <d:Name>Digital SLR</d:Name>
  26:                     </m:properties>
  27:                 </content>
  28:             </entry>
  29:         </m:inline>
  30:     </link>
  31:     <category term="CameraDatabaseModel.Camera" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  32:     <content type="application/xml">
  33:         <m:properties>
  34:             <d:CameraId m:type="Edm.Int32">1</d:CameraId>
  35:             <d:CameraTypeId m:type="Edm.Int32">3</d:CameraTypeId>
  36:             <d:Description>Entry-level digital SLR</d:Description>
  37:             <d:Price m:type="Edm.Double">40000</d:Price>
  38:             <d:ManufacturerName>Canon</d:ManufacturerName>
  39:             <d:MegaPixels m:type="Edm.Int16">10</d:MegaPixels>
  40:             <d:ModelName>EOS DigitalRebel XTi</d:ModelName>
  41:         </m:properties>
  42:     </content>
  43: </entry>

Here are a few other things we can do through the URL:

http://localhost:59407/cameraservice.svc/Cameras?$filter=Price lt 10000 – to return cameras priced less than (lt) 10000
http://localhost:59407/cameraservice.svc/CameraTypes?$orderby=Nameto order the CameraType entities by Name
http://localhost:59407/cameraservice.svc/Cameras(1)/CameraType – to get the details of the CameraType of a Camera whose Id is 1
http://localhost:59407/cameraservice.svc/Cameras?$skip=1&$top=2 – to perform some paging

You can also set some default paging in the InitializeService() method.

   1: // enables paging - 2 Camera entities on each call
   2: // config.SetEntitySetPageSize("Cameras", 2);

Interesting links:
http://www.odata.org/
http://blogs.msdn.com/b/endpoint/archive/2010/01/04/wcf-data-services-ria-services-alignment-questions-and-answers.aspx
http://blog.tonysneed.com/2010/04/13/wcf-data-services-versus-wcf-soap-services/
http://social.msdn.microsoft.com/Forums/en/wcf/thread/a3424ba0-c50f-4d89-92f3-eaf945cd9877
http://accessindepth.blogspot.com/2011/03/what-is-odata-and-why-should-i-care.html
http://blogs.msdn.com/b/endpoint/archive/2010/01/04/wcf-data-services-ria-services-alignment-questions-and-answers.aspx

Source code for this post. I plan to write a couple more articles on this, so I have gone a little ahead in the source application.

1 Comment

Comments have been disabled for this content.