Microsoft Data
Platform Development Technical Article
Mike Flasko
Microsoft® Corporation
Published: August 2008
Applies To:
- ADO.NET Data Services
- Visual Studio 2008 SP1
- .NET Framework 3.5 SP1
Summary: This
document describes how to create and use Microsoft® ADO.NET Data Services, and
discusses various details around the URI and payload formats. This document is
meant as an introduction to ADO.NET Data Services and thus covers the core
aspects of the technology, defering discussion of more advanced topics to
companion documents.
Introduction:
The goal of Microsoft® ADO.NET Data Services is to enable
applications to expose data as a data service that can be consumed by web
clients within corporate networks and across the internet. A data service is
reachable via regular HTTP requests, using standard HTTP verbs such as GET,
POST, PUT and DELETE to perform CRUD operations against the service. The
payload format used by the service is controllable by the application, but all
options are simple, open formats such as JSON and Atom/APP.
The use of web-friendly technologies make ADO.NET Data
Services ideal as a data back-end for AJAX-style applications, Rich Interactive
Applications and other applications that need to operate against data that is
stored across the web.
Getting Started: Creating Data Services
Pre-requisites
In order to create a data service using ADO.NET Data
Services in your own environment you will need Microsoft Visual Studio 2008
SP1. If you will be using your data service to access data stored in a
relational database, you will also need a database with updated data-access
providers, such as Microsoft SQL Server 2005 or 2008 (any edition, including
SQL Server Express, will work). Updated providers are also available for third
party database such as Oracle and DB2. For more information on third party
providers see http://msdn.microsoft.com/data.
The ADO.NET Entity Framework runtime and associated tools
are included in Visual Studio 2008 SP1.
Selecting a Data Source
The ADO.NET Data Service server framework is comprised of
two halves. The top-half is the runtime itself; this part is “fixed”, and it
implements URI translation, the Atom/JSON wire formats, the interaction
protocol, etc. This is what makes an ADO.NET Data Service look like an ADO.NET
Data Service. The bottom half is the data-access layer and is pluggable.
Communication between layers happens in terms of the IQueryable interface plus
a set of conventions to map CLR graphs into the URI/payload patterns of ADO.NET
Data Services.
The first step in creating an ADO.NET Data Service is to
determine the data source that is to be exposed as a set of REST-based
endpoints (ie. select or create a data access layer). For relational data stored in Microsoft SQL
Server or other 3rd Party databases, ADO.NET Data Services currently enables
easily exposing a conceptual model created using the ADO.NET Entity Framework
(EF). For all other data sources (XML
document, web service, application logic layer, etc) or to use additional database
access technologies (ex. LINQ to SQL), a mechanism is provided which enables
any data source, as per the plug-in model described above, to be exposed as an
ADO.NET Data Service.
To create a data service which exposes a relational database
through an Entity Framework conceptual model see “Creating a Data Service using
the ADO.NET Entity Framework”. To create a data service which exposes another
data source see “Creating a Data Service from any Data Source”.
Creating a Data Service using the ADO.NET Entity
Framework
ADO.NET Data Services are a specialized form of Windows
Communication Foundation services, and thus can be hosted in various environments.
The below example will create an ADO.NET Data Service which is hosted inside an
ASP.NET site. In order to create a data service, you must first create a web
project; you will then need to establish a connection with the database that will
be exposed by the service, and then create the data service itself within the
web application. Below is a step-by-step description of this process.
NOTE: These steps are for
Visual Studio Standard, Professional and Team System editions. If using Visual
Studio Web Developer, create a new “Web Site” rather than a “Web Application”.
The remaining workflow does not change. While this example uses a web
application, other project types (websites) and hosting mechanisms are also
supported.
- Create
the project
- Create
a “Web Application” project by going to the File menu in Visual Studio and
choosing New
Project. When the New Project window appears, choose either Visual Basic or
Visual C# as the programming language. Within the language category click on
“Web”, and select “ASP.NET Web Application” from the right-hand panel. Choose a
name for the project, for example SimpleDataService, and click OK.NOTE:
- Create
an Entity Data Model representation of your database using the ADO.NET Entity
Framework
- Assuming
that you already have a database that you want to expose as a data service, we
will create an Entity Data Model schema that maps 1:1 with your database.
Select SimpleDataService
Add New Item in Visual Studio. The Add New Item window will appear, choose
“ADO.NET Entity Data Model”, give it a name and click Add. We will use the
Northwind sample database throughout this example, so we will use “Northwind”
as our name (Northwind.edmx being the generated file name).
- In the rest of the step-by-step
guide we will assume you are using the Northwind sample database, so the
database connection created here should point to Northwind.NOTE:
- Create
a data service
- To
create the data service itself, select SimpleDataService
Add New Item in Visual Studio. Choose “ADO.NET Data Service” from the list of
item templates, give it a name (e.g. Northwind) and click add. NOTE:
- Visual
Studio will open the code file for the new service by default. You can also
find the file in the Solution Explorer; it will have the name you indicated,
with a “.svc.cs” or “.svc.vb” extension. Add an “imports” (Visual Basic”) or
“using” (C#) clause at the beginning of the file to include the namespace of
the data classes generated by the wizard in step 2.b.
You can use the Visual Studio object browser to locate the name, or you can
rely on intellisense. By default the namespace is derived from the database
that was used for the data service, unless changed when the EDM is created, for
example if the database was called Northwind the namespace will be
NorthwindModel.
- Locate
the “TODO: put your data source class name here” comment that indicates to put
in the class name that represents the database, and replace it with the name of
the class that was generated by the Entity Data Model Wizard in step 2.b.
Again, the name is derived from the database name, unless changed, so for
example if the database is called “Northwind” the class will be
“NorthwindEntities”.
- Enable
access to the data service
- By
default a data service does not expose any resources.For security purposes, access to resources
needs to be explicitly enabled before any resources or associations are
accessible. To enable read and write access to all resources in the Entity Data
Model associated with the service; locate the InitializeService method as shown
in Example 1 below.
The code file (e.g. northwind.svc.cs) should look more or
less like the example below.
using System;
using System.Web;
using System.Collections.Generic;
using System.ServiceModel.Web;
using System.Linq;
using System.Data.Services;
using NorthwindModel;
namespace SimpleDataService
{
public class Northwind : DataService<NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
}
}
Example 1: Basic data service in C#
To test the data service, simply hit Ctrl+F5 within Visual
Studio, which will start the development web server and will run the Data
Services server inside it. See the “Trying an ADO.NET Data Service” section
below to walk through a trial run of your newly created data service.
Creating a data service from any data source
In general, ADO.NET Data Services work by converting a
request to perform an operation over a resource, identified by a URI, to the
equivalent operation on the data model it represents. When the data model is backed by a relational
database, URIs are converted to Entity Framework Object Services method calls
(as per the section ‘Creating a local data service using the ADO.NET Entity
Framework’ above). Since that approach
is specific to the Entity Framework, a different method is required when the
underlying data model is backed by a data source other than a relational
database (XML Document, web service, etc).
In this case, requests to URIs are converted to LINQ queries. To enable this approach, ADO.NET Data
Services maps objects implementing the IQueryable interface in a CLR object
graph to Entity Sets. This approach
enables the ADO.NET Data Services Framework to expose any data source which has
an IQueryable provider written for it.
In order to enable such layering (ADO.NET Data Services over IQueryable-based
data sources) a mapping is defined between CLR objects and artifacts of the
EDM-based data model used by the ADO.NET Data Services Framework.
The remainder of this section defines by example the mapping
between CLR constructs and artifacts of an EDM-based data model. How to define classes implementing the
IQueryable interface such that they are represented as Entity Sets in the
service is shown below; however, IQueryable implementation specifics will be
covered in a separate document.
NOTE: The steps below
walk through creating an ADO.NET Data Service based on a CLR object graph.
These steps produce a data service created over an in memory collection of
objects. This approach is done as a simple way to show how an arbitrary CLR
graph can be exposed as a service and is handy for mocking data sources. In a
typical production deployment, the IQueryable properties shown representing
Entity Sets would not expose in memory data, but rather would translate the
IQueryable expression trees to data source specific queries.
- Create
the project
- Create
a “Web Application” project by going to the File menu in Visual Studio and
choosing New
Project. When the New Project window appears, choose either Visual Basic or
Visual C# as the programming language, and within the language category click
on “Web”, and select “ASP.NET Web Application” from the right-hand panel.
Choose a name for the project, for example CustomDataService, and click OK.NOTE:
- Create
a data service
- To
create the data service itself, select CustomDataService
Add New Item in Visual Studio. Choose “ADO.NET Data Service” from the list of
item templates, give it a name (e.g. contacts) and click add. NOTE:
- Copy
& paste the code from Example 2 into the code file opened in step 2.b.
- In
the CustomDataService namespace you should now have a class called
‘ContactsData’ which has two public properties (Contacts & Users) which
return IQueryable<Contact> and IQueryable<User>.These properties represent Entity Sets in the
CLR model.The User and Contact classes
in turn define the base Entity Types for each of the Entity Sets.
- Enable
access to the data service
- By
default a data service does not expose any resources.For security purposes, access to resources
needs to be explicitly enabled before any resources or associations are
accessible. To enable read and write access to all resources in the Entity Data
Model associated with the service, locate the InitializeService method and
ensure it matches what is shown in Example 2 below.
- Try
the data service
- Press
F5 to run the project and navigate to /contacts.svc.This returns the root document for the
service.Navigating to
/contacts.svc/Users will return all users.
- See
the ‘Trying an ADO.NET Data Service’ section below
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Linq;
namespace CustomDataService
{
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Contact> Contacts{get; set;}
}
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class ContactsData : //IUpdatable
{
#region Populate Service Data
static User[] _users;
static Contact[] _contacts;
static ContactsData()
{
_users = new User[]{
new User(){ ID=0, Name="Mike",Contacts= new List<Contact>()},
new User(){ ID=1, Name="Saaid",Contacts= new List<Contact>()},
new User(){ ID=2, Name="John",Contacts= new List<Contact>()},
new User(){ ID=3, Name="Pablo",Contacts= newList<Contact>()}};
_contacts = new Contact[]{
new Contact(){ ID=0, Name="Mike", Email="mike@contoso.com" },
new Contact(){ ID=1, Name="Saaid", Email="Saaid@hotmail.com"},
new Contact(){ ID=2, Name="John", Email="j123@live.com"},
new Contact(){ ID=3, Name="Pablo", Email="Pablo@mail.com"}};
_users[0].Contacts.Add(_contacts[0]);
_users[0].Contacts.Add(_contacts[1]);
_users[1].Contacts.Add(_contacts[2]);
_users[1].Contacts.Add(_contacts[3]);
}
#endregion
public IQueryable<User> Users
{
get { return _users.AsQueryable<User>(); }
}
public IQueryable<Contact> Contacts
{
get { throw new DataServiceException(403, "Requests directly to
/Contacts are not allowed");}
}
public class contacts : DataService<ContactsData>
{
// This method is called only once to initialize
//service-wide policies.
public static void InitializeService(IDataServiceConfiguration
config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
//Service operations, query interceptors & change interceptors go here
}
}
Example 2: ADO.NET Data Service exposing an in-memory
data source
Trying an ADO.NET Data Service
The easiest way to test your ADO.NET Data Service is to
simply access it with a web browser. While this is probably not the way you
will ultimately use the data service (it is more likely that a program will
interact with it), it is an easy way to understand how requests work, what
results look like, and other details surrounding the implementation of the
service.
To interact with the data service, open a web browser such
as Microsoft Internet Explorer and point it to the URL that is the entry point
to the site. If you created the data service locally using Visual Studio, you
can simply hit Ctrl+F5 to start the web server, and then point the URL in the
browser that Visual Studio launches to the data service file. For example,
“http://host/vdir/northwind.svc” where “host” is a computer name or localhost;
you may also need to add a port number.
NOTE: To view Atom (the
default format returned by an ADO.NET Data Service) in Internet Explorer, you
must first ensure that Feed Reading View is turned off. This can be done on the
Content tab of Tools | Internet Options.
When you hit the entry point, (after setting the Entity Set
access rules as per the examples above) the response is an XML response that
contains the list of Entity Sets exposed by the data service. Since the default serialization used by a
data service is Atom, the document returned by default is an Atom service
document (as shown in the examples below).
If you are using the full Northwind sample database, the output will be
similar to what is shown in Example 3 below.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<service
xml:base="http://localhost:51905/nw.svc/"
xmlns:atom=http://www.w3.org/2005/Atom
xmlns:app="http://www.w3.org/2007/app"
xmlns="http://www.w3.org/2007/app">
<workspace>
<atom:title>Default</atom:title>
<collection
href="Categories">
<atom:title>Categories</atom:title>
</collection>
<collection
href="CustomerDemographics">
<atom:title>CustomerDemographics</atom:title>
</collection>
<collection
href="Customers">
<atom:title>Customers</atom:title>
</collection>
<collection
href="Employees">
<atom:title>Employees</atom:title>
</collection>
<collection
href="Order_Details">
<atom:title>Order_Details</atom:title>
</collection>
<collection
href="Orders">
<atom:title>Orders</atom:title>
</collection>
<collection
href="Products">
<atom:title>Products</atom:title>
</collection>
<collection
href="Region">
<atom:title>Region</atom:title>
</collection>
<collection
href="Shippers">
<atom:title>Shippers</atom:title>
</collection>
<collection
href="Suppliers">
<atom:title>Suppliers</atom:title>
</collection>
<collection
href="Territories">
<atom:title>Territories</atom:title>
</collection>
</workspace>
</service>
Example 3: Response for the root of a data service
Using this as a starting point, you can start to browse the
contents of the data service. To continue with the Northwind example, you can
add “/Products” to the URL, resulting in “http://host/northwind.svc/Products”,
which returns all of the products in the store. Example 4 below shows a partial
listing of the result.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<feed xml:base="http://host/northwind.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservicesdataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservicesdataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<id>http://host/northwind.svc/Products</id>
<updated />
<title type=”text”>Products</title>
<link rel="self" href="Products"
title="Products" />
<entry>
<id>http://host/northwind.svc/Products(1)</id>
<updated />
<title />
<author>
<name />
</author>
<link rel="edit"
href="Products(1)" title="Products" />
<category
term="NorthwindModel.Products"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content
type="application/xml">
<m:properties>
<d:ProductID
m:type="Edm.Int32">1</d:ProductID>
<d:ProductName>Chai</d:ProductName>
<d:QuantityPerUnit>10
boxes x 20 bags</d:QuantityPerUnit>
<d:UnitPrice
m:type="Edm.Decimal">18.0000</d:UnitPrice>
<d:UnitsInStock
m:type="Edm.Int16">39</d:UnitsInStock>
<d:UnitsOnOrder
m:type="Edm.Int16">0</d:UnitsOnOrder>
<d:ReorderLevel
m:type="Edm.Int16">10</d:ReorderLevel>
<d:Discontinued
m:type="Edm.Boolean">false</d:Discontinued>
</m:properties>
</content>
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Categories"
title="Categories"
href="Products(1)/Categories"
type="application/atom+xml;type=entry" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
title="Order_Details" href="Products(1)/Order_Details"
type="application/atom+xml;type=feed" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Suppliers"
title="Suppliers" href="Products(1)/Suppliers"
type="application/atom+xml;type=entry" />
</entry>
<entry> … </entry>
</feed>
Example 4: Listing of the contents of an entity
set, in Atom/APP format
If you want to construct a URL that points to a particular
entity, such as a particular Product in this example, you can do so by adding
the key value in parenthesis (the URL of a given entity can be obtained by
composing the name of the entity instance element, and the base URL of the XML
document). For example, the URL “http://host/northwind.svc/Products(1)” would
produce the results shown in Example 5 below.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<entry xml:base="http://localhost:63952/northwind.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">
<id>http://localhost:63952/northwind.svc/Products(1)</id>
<title type="text" />
<updated>2008-06-24T01:30:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Products"
href="Products(1)" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Categories"
type="application/atom+xml;type=entry" title="Categories"
href="Products(1)/Categories" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
type="application/atom+xml;type=feed" title="Order_Details"
href="Products(1)/Order_Details" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Suppliers"
type="application/atom+xml;type=entry" title="Suppliers"
href="Products(1)/Suppliers" />
<category term="NorthwindModel.Products"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content
type="application/xml">
<m:properties>
<d:ProductID
m:type="Edm.Int32">1</d:ProductID>
<d:ProductName>Chai</d:ProductName>
<d:QuantityPerUnit>10
boxes x 20 bags</d:QuantityPerUnit>
<d:UnitPrice
m:type="Edm.Decimal">18.0000</d:UnitPrice>
<d:UnitsInStock
m:type="Edm.Int16">39</d:UnitsInStock>
<d:UnitsOnOrder
m:type="Edm.Int16">0</d:UnitsOnOrder>
<d:ReorderLevel
m:type="Edm.Int16">10</d:ReorderLevel>
<d:Discontinued
m:type="Edm.Boolean">false</d:Discontinued>
</m:properties>
</content>
</entry>
Example 5: Response for a single-entity URL
If you look at the values of the properties on the entity in
Example 5, you will notice that some of them such as ProductName and UnitPrice
are scalar values, where as others such as Categories and Suppliers are
different (they are part of a link element). The latter ones represent
“navigation properties”, properties that navigate from an entity (Product in
this case) to a related entity (the product’s category or its supplier in this
case). URLs can be composed to traverse this relationship. For example, to see
the supplier for this product we would use:
http://host/northwind.svc/Products(1)/Suppliers
In this section, we have gone through a quick overview of
how to create and interact with data services. Please see the additional
sections for a deeper look at ADO.NET Data Services.
Finding and Pointing to Data: URLs in Data Services
ADO.NET Data Services defines a simple, yet very expressive,
URL format that allows applications to point to sets of entities and individual
entities, as well as to traverse relationships between entities. Several
options can also be added as query string parameters to control how the data is
presented.
Structure of Web Data Services URLs
The basic format for URLs is:
http://host/<service>/<EntitySet>[(<Key>)[/<NavigationProperty>[(<Key>)/...]]]
NOTE: In the syntax
above, [ ] imply optional components
The 3 main elements of the URL syntax are:
- The data service URI.The data service URI is the first part of the
URL that points to the root of the data service.This will typically be (but is not limited
to) a .svc file. For example, http://host/myapp/northwind.svc. The examples below
assume that the URLs start with that prefix for brevity.
- The entity-set name (optional).If you include an entity-set name, then all
the entities in that entity-set are returned. For example, /Customers would
return all of the customers in the Northwind data service. The system also allows
for an optional filter predicate contained in parenthesis to subset the
response to a single entity.For
single-key entities, you can simply indicate the key value, and the resulting
URL will point to that entity specifically. For example, if there is a customer
entity with a string-based key ‘ALFKI’, its URL would be
/Customers(‘ALFKI’).Additional
expression-based filtering on a set is enabled by using query string
parameters, which are described later in this document
- A navigation property (optional).A navigation property can be placed after the
entity-set name (separated by a “/”), indicating that you want to traverse the
relationship being pointed to. For example, /Customers(‘ALFKI’)/Orders would
return the sales orders of the customer with the primary key ‘ALFKI’. Similar
to the entity set name, a filter can also be applied to the navigation property
using query string operators (described later in this document) to return only
a subset of the related entities. For example, /Customers(‘ALFKI’)/Orders?$filter=OrderDate
gt '1998-1-1' returns all the orders posted after Jan 1st, 1998, for the
customer with a key ‘ALFKI’. Since the result of traversing a relationship
through a navigation property is another set of entities, you can continue to
add navigation properties to the URL to navigate through the relationship graph
specified in the data service schema. For example,
/Customers(‘ALFKI’)/Orders(1)/Employees returns the employees that created
sales order 1 for the customer with a key of ‘ALFKI’.
Query string options
While the URL format allows for filtering and traversing
through the graph of entities in the store, it does not have constructs to
control the output. For that, a number of optional query string parameters are
supported by ADO.NET Data Services. Table 1 below lists all of the query
options along with their description and some usage examples.
Table 1: Query string options
Expression Syntax
The simple expression language that is used in filter
operators (and also supported in orderby operations) supports references to
columns and literals. The literal values can be strings enclosed in single
quotes, numbers and boolean values (true or false) or any of the additional
literal representations shown in the ‘Data Type Literal Representations’ section
below. The operators in the expression language use abbreviations of the names rather
than symbols to reduce the amount of escaping necessary in the URL. The
abbreviations are listed in Table 2.
Table 2: Operators for filter expressions
In addition to the operators described above, a set of
functions are also defined for use with the filter query string operator. The following tables list the available
functions. This release does not support
Aggregate functions (sum, min, max, avg, etc) as they would change the meaning
of the ‘/’ operator to allow traversal through sets. For example,
/Customers?$filter=average(Orders/Amount) gt 50.00 is not supported. Additionally, ISNULL or COALESCE operators
are not defined. Instead, there is a null literal which can be used for
comparison following CLR semantics.
Table 3: String Functions
Table 4: Date Functions
Table 5: Math Functions
Table 5: Type Functions
Examples
- /Orders?$filter=ID eq 1From
- From all the Orders in the data store, return
only the Orders with the ‘ID’ property equal to 201
- /Customers?$filter='Wayne, John' eq
insert(fullname, length(lastname), ',')
- Match all Customers with the value of the
property ‘fullname’ equal to ‘Wayne, John’
- /Customer$filter=isof(‘SpecialCustomer’)
- Match all customers that are of type
SpecialCustomer.Entity sets support
inheritance and thus customer entities in the entity set may be of different
types within a type hierarchy
Data Type Literal Representations
Each of the supported data types in ADO.NET Data Services
has a literal form defined. This literal
form is used in URLs generated and accepted by the ADO.NET Data Services
Framework to identify the data type of a literal. The literal form of each supported data type
is shown in Table 6 below.
Table 6: Literal Forms for Supported Data Types
Options for Data Representation
ADO.NET Data Services currently supports exchanging entities
in JSON and Atom (an XML- based feed format). In all cases, the same format can
be used both to receive information from the data service as well as to send to
it.
Which format is best depends largely on the nature of the
application and the runtime environment being used. For example, AJAX-based
applications that run inside web browsers may find the JSON format easier to
use, as it can be easily turned into JavaScript objects. On the other hand, a
client application may be written with a language/runtime library that has a
built-in XML parser, making the Atom format would be a good choice.
The mechanism used to specify in which format information is
sent to a data service is the “Content-Type” HTTP header. Following the HTTP
RFC 2616, the mechanism used to control the format returned by the system is
the “Accept” HTTP header.
Table 7: Supported payload formats
Atom format details
The Atom Publishing Protocol is an HTTP-based approach for
creating and editing Web resources. It is designed fundamentally around the
idea of using the basic operations provided by the HTTP protocol (such as GET,
PUT, POST and DELETE) to pass around instances of Atom 1.0 Feed and Entry
documents that represent things like blog entries, podcasts, wiki pages,
calendar entries and so on.
To enable Atom serialization in a data service, a fixed
mapping from entities to Atom constructs is defined. For responses from the data service, the
response can be either a set or a single entity. Entity sets are represented as
<atom:feed> elements and single entities as <atom:entry> elements.
For requests to the data service, see the section “Changing Data in Web Data
Services” below.
Each property of an Entity is represented by an element
within the <atom:content> element. The <atom:content> element is a
child element of <atom:entry>. The
value of the property is included as the content of the associated element.
Each <atom:entry> element has an <atom:ID> element that contains
the URL to the entity. URLs are given in absolute form as per the Atom
specification. Each response also
includes an “xml:base” attribute that
provides the base to resolve the relative URLs within the document. A
hypothetical Customer entity for /Customers(‘ALFKI’) with no relationships is
shown in Example 6.
<?xml
version="1.0" ?>
<entry
xmlns="http://www.w3.org/2005/Atom"
xml:base=http://server/service.svc
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<title/>
<id>http://server/service.svc/Customer(’ALFKI’)</id>
<updated/>
<category term="NorthwindModel.Customers"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content type=”application/xml”>
<m:properties>
<d:CustomerID>ALFKI</d:CustomerID>
<d:CompanyName> Alfreds
Futterkiste</d:CompanyName>
<d:Address> Obere Str. 57</d:Address>
</m:properties>
</content>
</entry>
Example 6: A single-entity response from the
data service.
If the URL specified in the request matches more than one
entity, the entities are returned inside an <atom:feed> element, as illustrated
by the partial result listing for /Customers in Example 7 below.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<feed
xml:base="http://server/service.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservicesdataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<id>http://server/service.svc/Customers</id>
<updated />
<title>Customers</title>
<link rel="self" href="Customers"
title="Customers" />
<entry>
<id>http://server/service.svc/Customers('ALFKI')</id>
<updated />
<title />
<author>
<name />
</author>
<category term="NorthwindModel.Customers"
scheme=http://schemas.microsoft.com/ado/2007/08/dataservices/scheme
/>
<link rel="edit" href="Customers('ALFKI')"
title="Customers" />
<content
type="application/xml">
<m:properties>
<d:CustomerID>ALFKI</d:CustomerID>
<d:CompanyName>Alfreds
Futterkiste</d:CompanyName>
<d:ContactName>Maria
Anders</d:ContactName>
<d:ContactTitle>Sales
Representative</d:ContactTitle>
<d:Address>Obere Str.
57</d:Address>
<d:City>Berlin</d:City>
<d:Region
d:null="true" />
<d:PostalCode>12209</d:PostalCode>
<d:Country>Germany</d:Country>
<d:Phone>030-0074321</d:Phone>
<d:Fax>030-0076545</d:Fax>
<m:properties>
</content>
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Orders"
title="Orders"
href="Customers('ALFKI')/Orders"
type="application/atom+xml;type=feed" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CustomerDemographics"
title="CustomerDemographics"
href="Customers('ALFKI')/CustomerDemographics"
type="application/atom+xml;type=feed" />
</entry>
// Additional <entry> elements
</feed>
Example 7: A response that contains multiple
entities.
For entities that have relationships, a navigation property
is included and represented using an <atom:link
rel=”http://schemas.microsoft.com/ado/2007/08/dataservices/related/[NavProp]”>
element. This element contains a URL
pointing to the related entities (the related entities themselves are not
included), as shown in Example 7 (Note the “Orders” navigation property).
If the request includes an option to expand one or more navigation
properties, such as /Customers(‘ALFKI’)?$expand=Orders, the related entities
are nested under the <atom:link rel=”related”> element representing the
navigation property, as shown below in Example 8.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<?xml version="1.0" encoding="utf-8"
standalone="yes" ?>
<entry xml:base="http://localhost:63952/northwind.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">
<id>http://localhost:63952/northwind.svc/Customers('ALFKI')</id>
<title type="text" />
<updated>2008-06-24T02:08:05Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Customers"
href="Customers('ALFKI')" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Orders"
type="application/atom+xml;type=feed" title="Orders"
href="Customers('ALFKI')/Orders">
<m:inline>
<feed>
<title type="text">Orders</title>
<id>http://localhost:63952/northwind.svc/Customers('ALFKI')/Orders</id>
<updated>2008-06-24T02:08:05Z</updated>
<link
rel="self" title="Orders"
href="Customers('ALFKI')/Orders"
/>
<entry>
<id>http://localhost:63952/northwind.svc/Orders(10643)</id>
<title
type="text" />
<updated>2008-06-24T02:08:05Z</updated>
<author>
<name />
</author>
<link
rel="edit" title="Orders" href="Orders(10643)"
/>
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customers"
type="application/atom+xml;type=entry"
title="Customers"
href="Orders(10643)/Customers" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Employees"
type="application/atom+xml;type=entry" title="Employees"
href="Orders(10643)/Employees" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
type="application/atom+xml;type=feed" title="Order_Details"
href="Orders(10643)/Order_Details" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Shippers"
type="application/atom+xml;type=entry" title="Shippers"
href="Orders(10643)/Shippers" />
<category term="NorthwindModel.Orders"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content
type="application/xml">
<m:properties>
<d:OrderID
m:type="Edm.Int32">10643</d:OrderID>
<d:OrderDate
m:type="Edm.DateTime">1997-08-
25T00:00:00</d:OrderDate>
<d:RequiredDate
m:type="Edm.DateTime">1997-09-22T00:00:00</d:RequiredDate>
<d:ShippedDate
m:type="Edm.DateTime">1997-09- 02T00:00:00</d:ShippedDate>
<d:Freight
m:type="Edm.Decimal">29.4600</d:Freight>
<d:ShipName>Alfreds
Futterkiste</d:ShipName>
<d:ShipAddress>Obere
Str. 57</d:ShipAddress>
<d:ShipCity>Berlin</d:ShipCity>
<d:ShipRegion
m:null="true" />
<d:ShipPostalCode>12209</d:ShipPostalCode>
<d:ShipCountry>Germany</d:ShipCountry>
</m:properties>
</content>
</entry>
//Additional <entry> elements
</feed>
</inline>
</link>
<category term="NorthwindModel.Customers"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content type="application/xml">
<m:properties>
<d:CustomerID>ALFKI</d:CustomerID>
<d:CompanyName>Alfreds
Futterkiste</d:CompanyName>
<d:ContactName>Maria
Anders</d:ContactName>
<d:ContactTitle>Sales
Representative</d:ContactTitle>
<d:Address>Obere Str.
57</d:Address>
<d:City>Berlin</d:City>
<d:Region
m:null="true" />
<d:PostalCode>12209</d:PostalCode>
<d:Country>Germany</d:Country>
<d:Phone>030-0074321</d:Phone>
<d:Fax>030-0076545</d:Fax>
</m:properties>
</content>
</entry>
Example 8: Response with nested related entities using
the "expand" option.
In addition to the regular format for exchanging entities
and the per service metadata description available from each data service, Atom
defines the concept of a service document which describes all the sets
available from the service. ADO.NET Data
Services will expose a service document from the URI representing the root of
the service (ex. http://server/service.svc).
The service document lists the URIs representing each of the entity sets
available from the data service.
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<service
xml:base="http://server/service.svc/"
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"
xmlns="http://www.w3.org/2007/app">
<workspace>
<atom:title>Default</atom:title>
<collection
href="Categories">
<atom:title>Categories</atom:title>
</collection>
<collection
href="CustomerDemographics">
<atom:title>CustomerDemographics</atom:title>
</collection>
<collection
href="Customers">
<atom:title>Customers</atom:title>
</collection>
<collection
href="Employees">
<atom:title>Employees</atom:title>
</collection>
<collection
href="Order_Details">
<atom:title>Order_Details</atom:title>
</collection>
<collection
href="Orders">
<atom:title>Orders</atom:title>
</collection>
<collection
href="Products">
<atom:title>Products</atom:title>
</collection>
<collection
href="Region">
<atom:title>Region</atom:title>
</collection>
<collection
href="Shippers">
<atom:title>Shippers</atom:title>
</collection>
<collection
href="Suppliers">
<atom:title>Suppliers</atom:title>
</collection>
<collection
href="Territories">
<atom:title>Territories</atom:title>
</collection>
</workspace>
</service>
Example 9: Atom service document as returned from
an ADO.NET Data Service.
JSON format details
The JSON format follows the data encoding described in the
RFC 4627 to represent data using a subset of the JavaScript syntax. For more
information on JSON see RFC 4627 in the IETF web site (http://tools.ietf.org/html/rfc4627)
and the http://json.org web site.
The JSON format in
ADO.NET Data Services also uses a fixed mapping from entities. Entities
themselves are mapped to a JSON object, where each property of the object
represents a property of the entity. For requests, a single object is expected
by the data service. For responses, arrays are returned when sets are retrieved
and a single JSON object when sets are filtered by key. To guard against Cross-Site Request Fogery
(CSRF) attacks using JSON, the array or JSON object in a response is always
wrapped by a single JSON object with a single member named “d” whose value
includes the array or object representing the entity or entities being
returned.
In addition to the set of properties that correspond to the
entity, each JSON object also includes a member called “__metadata” (double
underscore prefix). This member carries metadata for each entity exchanged
between the client and server. The members of the metadata object are listed in
Table 6.
Table 8: Members of the "__metadata" object
in JSON format
Example 10 shows the response from a data service, over the
Customers entity-set in the Northwind database, for a URL that requests a
single entity back (/Customers(‘ALFKI’)).
{
"d" : {
__metadata: {
uri:
"http://server/service.svc/Customers(\'ALFKI\')",
type:
"NorthwindModel.Customers"
},
CustomerID: "ALFKI",
CompanyName: "Alfreds
Futterkiste",
ContactName: "Maria Anders",
ContactTitle: "Sales
Representative",
Address: "Obere Str. 57",
City: "Berlin",
Region: null,
PostalCode: "12209",
Country: "Germany",
Phone: "030-0074321",
Fax: "030-0076545",
Orders: {
__deferred: {
uri:
"http://server/service.svc/Customers(\'ALFKI\')/Orders"
}
},
CustomerDemographics: {
__deferred: {
uri:
“http://server/service.svc/Customers(\'ALFKI\')/CustomerDemographics"
}
}
}
}
Example 10: JSON response from a data service for a single 'Customer' entity
If more than one entity matches the criteria, the response
from the data service is similar to the single-entity case shown in Example 10
above, except that an array of JSON objects is returned instead of a single
object.
If the entity has navigation properties, such as Orders in
Example 10, by default the value of the property is the __deferred object,
which contains the URI that can be used to retrieve the related entities.
If a hierarchy is returned by the data service as a result
of using the “expand” option, objects are nested in the JSON payload. This
results in the proper references being created between JavaScript objects
during de-serialization by the consumer of the data. Example 11 shows a
Customer entity with expanded sales orders.
{
"d" : {
__metadata: {
uri:
"http://server/service.svc/Customers(\'ALFKI\')",
type:
"NorthwindModel.Customers"
},
CustomerID: "ALFKI",
CompanyName: "Alfreds
Futterkiste",
ContactName: "Maria Anders",
ContactTitle: "Sales
Representative",
Address: "Obere Str. 57",
City: "Berlin",
Region: null,
PostalCode: "12209",
Country: "Germany",
Phone: "030-0074321",
Fax: "030-0076545",
Orders: [
{
__metadata: {
uri:
"http://server/service.svc/Orders(10643)",
type:
"NorthwindModel.Orders"
},
OrderID: 10643,
OrderDate:
"\/Date(872467200000)\/",
RequiredDate:
"\/Date(874886400000)\/",
ShippedDate:
"\/Date(873158400000)\/",
Freight: "29.4600",
ShipName: "Alfreds
Futterkiste",
ShipAddress: "Obere Str.
57",
ShipCity: "Berlin",
ShipRegion: null,
ShipPostalCode: "12209",
ShipCountry: "Germany",
Customers: {
__deferred: {
uri:
"http://server/service.svc/Orders(10643)/Customers"
}
},
Employees: {
__deferred: {
uri:
"http://server/service.svc/Orders(10643)/Employees"
}
},
Order_Details: {
__deferred: {
uri:
"http://server/service.svc/Orders(10643)/Order_Details"
}
},
Shippers: {
__deferred: {
uri:
"http://localhost:51905/nw.svc/Orders(10643)/Shippers"
}
}
},
// Additional order objects
]
CustomerDemographics: {
__deferred: {
uri:
“http://server/service.svc/Customers(\'ALFKI\')/CustomerDemographics"
}
}
}
}
Example 11: A hierarchical result containing a
Customer and its related Sales Orders, in JSON format.
Changing Data in ADO.NET Data Services
The data services exposed by ADO.NET Data Services not only
allow you to obtain data, but also to change data in the store. Data services
provide a uniform mechanism for creating, updating and deleting entities, as
well as creating and deleting associations between entities.
Creating a new entity
To create a new entity, an HTTP POST request needs to be
sent to the URI that points to the container for that entity. For example, for
a data service created over the Northwind sample database in SQL Server, to
create a new Category object you would send an HTTP POST request to the
/Categories URI (e.g. http://host/vdir/northwind.svc/Categories).
The payload of the HTTP POST request is the entity data,
encoded in any of the supported formats. The “Content-Type” header in the HTTP
request needs to indicate the format so the data service knows how to interpret
the data appropriately. This behavior
for POST requests maps to that described in the AtomPub RFC. Not all the properties of a given entity type
need to be included; those not included will take their default value in the
server. If a given property is not nullable the create operation will fail.
Example 12 shows the payload of a POST request to create a new category, using
both the Atom and JSON formats.
ATOM:
<entry
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">
<content
type="application/xml">
<m:properties>
<d:CategoryName>New
Category</d:CategoryName>
</m:properties>
</content>
</entry>
JSON:
{
CategoryName: "New Category"
}
Example 12: Payloads for creating a new Category entity
using Atom and JSON
In the case of Example 12, the primary key for the entity
(CategoryID property) is declared in the model to be automatically generated by
the server (an IDENTITY column in SQL Server), so there is no need to specify
it here. If the key property (or properties) is not generated by the server
then they must also be specified in the payload.
After processing the POST request, the data service will
return the entity that was created, with all of its values updated to reflect
any changes that happened on the server, such as server-generated keys,
properties modified at the database level using triggers, etc. In addition to the entity returned in the
payload, a ‘Location’ HTTP response header is returned that includes the URI to
the newly created entity as per AtomPub RFC 5023.
ATOM:
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<entry
xml:base="http://host/ northwind.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">
<id>http://host/northwind.svc/Categories(11)</id>
<updated />
<title />
<author>
<name />
</author>
<link
rel="edit" href="Categories(11)"
title="Categories" />
<category
term="NorthwindModel.Categories"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content
type="application/xml">
<m:properties>
<d:CategoryID
m:type="Int32">11</d:CategoryID>
<d:CategoryName>NewCat2</d:CategoryName>
<d:Description d:null="true"
/>
<d:Picture m:type="Byte[]"
d:null="true" />
</m:properties>
</content>
<link
rel="
http://schemas.microsoft.com/ado/2007/08/dataservices/related/Products"
title="Products"
href="Categories(11)/Products"
type="application/atom+xml;type=feed" />
</entry>
JSON:
{
"d" : {
__metadata: {
uri: "http://host/northwind.svc/Categories(11)",
type:
"NorthwindModel.Categories"
},
CategoryID: 11,
CategoryName: "New Category",
Description: null,
Picture: null,
Products: {
__deferred: {
uri:
"http://host/northwind.svc/Categories(11)/Products"
}
}
}
}
Example 13: Response from the data service after
processing a POST request for creating a Category, in Atom and JSON formats
Updating existing entities using merge semantics
There are two possible options for updating existing
entities, a merge-based update or a replace-based update. An existing entity
can be modified (updated) by sending an HTTP MERGE request to the URI where the
entity is located. Following with the example for creating an entity, to modify
the Category entity with key ‘11’ from the Northwind data service you would use
the /Categories(11) URI.
In the case of merge-based updates, the payload needs to be
an entity and only needs to contain the properties that are being modified. If
a property is not included, the value that is currently present in the server
will be preserved. Example 14 shows the payload used to update the category
that was inserted in the previous example.
ATOM:
<entry
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">
<content
type="application/xml">
<m:properties>
<d:CategoryName>Changed
Category</d:CategoryName>
</m:properties>
</content>
</entry>
JSON:
{
CategoryName: "Changed Category"
}
Example 14: Payload used to modify an existing
Category entity through an HTTP PUT request, Atom and JSON formats
The response from HTTP MERGE requests is a 204 (No Content)
HTTP response. In future versions, we
may consider enabling responses similar to that returned from POST requests
where the response payload is the latest version of the entity.
Updating existing entities using replace semantics
An existing entity can be modified (updated) by sending an
HTTP PUT request to the URL where the entity is located. Following with the
example for creating an entity using a POST, to modify the Category entity with
key ‘11’ from the Northwind data service you would use the /Categories(11) URI.
In the case of replace-based updates, the payload needs to
be an entity and should contain all the properties of the entity (not including
navigation properties). If a property is not included, the value is reset on
the server to the default value for the property. This behavior for PUT requests maps to that
described in the AtomPub RFC 5023. Example 13
shows the payload used to update the category that was inserted in the previous
insert example. Since not all the
properties are included in the payload, those not specified will be reset to
their default values by the data service.
ATOM:
<entry
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">
<content
type="application/xml">
<m:properties>
<d:CategoryName>Changed
Category</d:CategoryName>
</m:properties>
</content>
</entry>
JSON:
{
CategoryName: "Changed Category"
}
Example 15: Payload used to modify an existing Category
entity through an HTTP PUT request, Atom and JSON formats
The response from an HTTP PUT request is a 204 (No Content)
HTTP response. In future, we may
consider enabling responses similar to that returned from POST requests where
the response payload is the latest version of the entity.
Deleting entities
Entities are deleted by executing an HTTP DELETE request
against the URL that identifies the entity in the data service. No payload is
necessary for a delete operation.
The data service response will not contain a payload. If the
entity was deleted successfully then the request will be