Microsoft Data
Platform Development Technical Article
Writers: Mike Flasko
Microsoft Corporation
Published: August 2008
Applies To:
Visual Studio 2008 SP1
.NET Framework 3.5 SP1
ADO.NET Data Services
Summary: The ADO.NET Data Services framework consists of a
combination of patterns and libraries that enable the creation and consumption
of data services for the web. This article will discuss the key elements of the
ADO.NET Data Services framework at a high level and the motivation behind its
design.
>>Introduction
>Driven by the need to provide richer
user experiences while maintaining the ease of deployment that is
characteristic of web content, the way web applications are architected is
changing dramatically.
The technological
shift towards AJAX-based frameworks and tools, and the trend for building Rich
Interactive Applications based on application environments such as Microsoft
Silverlight 2 and Adobe Flash, changes how presentation, behavior and data are delivered
to client web browsers.
In traditional web
applications, a server-side component would render HTML content that consisted
of presentation aspects (fonts, colors, and layout), the "data" or
content itself, and perhaps client-side code (typically JavaScript) to drive
interaction. All of this was delivered to the web browser through a single
mechanism and every interaction would require going back to the server
"with the whole page" for processing. Finally, another HTML page
would be rendered as the response.
One key trait of
the new architectures for web applications is that presentation and data are no
longer embedded in the same container and delivered through the same channel.
AJAX-based web sites serve pages containing presentation and behavior, and then
the JavaScript code that represents the page behavior turns back and fetches
data separately using XMLHTTP. Silverlight and Flash applications remove the
option of a server-side rendering process that mixes data and code; code to
drive the presentation aspects is pre-compiled and deployed as a single file in
the web server. After reaching the client web browser the code calls back into
to the web server to retrieve actual data to display within the user interface.
Orthogonal to
single-data source web applications and their architecture, a new class of
application has also emerged on the web, called mashups. Mashups are front-ends
that aggregate and combine data that is available in a "pure data
form" on the web – currently mostly are in the form of RSS/Atom feeds -
and add value on top of the individual pieces of data.
This landscape
makes it particularly interesting to talk about data services, the services
that applications use to find and manipulate data on the web, regardless of the
presentation technology used in the user interface or whether or not the
front-end is hosted on the same site as the server hosting the data.
ADO.NET Data Services for the Web
The ADO.NET Data Services framework consists of a
combination of patterns and libraries that enable the creation and consumption
of data services for the web.
The goal of the ADO.NET Data Services framework is to
facilitate the creation of flexible data services that are naturally integrated
with the web. As such, ADO.NET Data Services use URIs to point to pieces of
data and simple, well-known formats to represent that data, such as JSON and
ATOM (XML-based feed format). This results in the data service being surfaced
as a REST-style resource collection that is addressable with URIs and that agents
can interact with using standard HTTP verbs such as GET, POST, PUT or DELETE.
In order for the system to understand and leverage semantics
over the data that it is surfacing, ADO.NET Data Services models the data
exposed through the data service using a model called the Entity Data Model
(EDM), an Entity-Relationship derivative. This organizes the data in the form
of instances of "entity types", or "entities", and the
associations between them.
For relational data, ADO.NET Data Services supports exposing
an EDM model created using the ADO.NET Entity Framework. For all other (ie. non relational) data
sources or to use additional database access technologies (ex. LINQ to SQL) a
mechanism is provided which enables any data source to be modeled as entities
and associations (ie. described using an EDM schema) and exposed as a data
service.
This article will discuss the key elements of the ADO.NET
Data Services framework at a high level and the motivation behind its design.
>A simple addressing scheme for data with uniform
URIs
To describe the system by example we will use a
customer-tracking data service. The URI that will represent all of the Customer
entities in the example ADO.NET Data Service would be:
http://myserver/data.svc/Customers
In this context, the "/Customers" part of the URI
points to the Customers entity-set, which is the container for Customer
instances. It is also possible to point to a single entity using a URI, for
example:
http://myserver/data.svc/Customers(‘ALFKI’)
This URI results in a single Customer entity whose key, as
defined in the EDM schema, has a value of "ALFKI".
If the Customer entity includes properties, those properties
can be addressed individually. For
example, the following URI represents the Contact Name of the Customer entity
with key “ALFKI”:
http://myserver/data.svc/Customers(‘ALFKI’)/ContactName
Since EDM schemas not only describe the structure of the
entity types, but also the associations between them, the system can leverage
that information to provide a mechanism for association traversal. For example,
if each of the customers in the data service has a set of Sales Orders
associated with it, the following URI would represent the set of Sales Orders
associated with the Customer whose key has a value of “ALFKI”.
http://myserver/data.svc/Customers(‘ALFKI’)/Orders
It may be the case that an application wishes to operate on
an entities’ data as well as entities related to the target entity. For example, if an application wants to
display a master-details style grid, it likely needs to efficiently retrieve
both the target Customer and that Customers’ orders to display. The following URI addresses a single Customer
and states the Customers’ orders and alternate contact information is to be
returned inline in the same response from the data service.
http://myserver/data.svc/Customers(‘ALFKI’)?$expand=Orders,AlternateContacts
All of these URIs represent resources, such as "a
customer" or "the sales orders associated with the customer with the
key 'ALFKI'". In addition to key-based selection, the ADO.NET Data Service
URIs allow for simple predicates to be included. This enables the
representation of sets based on properties of the entities themselves, such as
"the set of active sales orders for the customer with key 'ALFKI'",
which in ADO.NET Data Service URI form is:
http://myserver/data.svc/Customers(‘ALFKI’)/Orders?$filter=Active
eq true
The $filter operator used in the above example may also be
used in conjunction with string, math or date functions such as in the following
URI, which represents “the set of active orders made in the year 2007 for the
customer ‘ALFKI’”:
http://myserver/data.svc/Customers(‘ALFKI’)/Orders?$filter=Active
eq true and (year(OrderDate) eq 2007)
If the Sales Orders entities have other related entities,
the URI can continue to drill down into the association graph, traversing
associations and filtering each of the resulting sets.
ADO.NET Data Service URIs can also include control
information in the query string, to adjust how data is presented to the caller.
Common requirements for web applications include aspects such as being able to
request data in pages and to sort the data by various properties. Continuing
with the example, the following URI lists the same Sales Orders but ordered by
the order date:
http://myserver/data.svc/Customers(‘ALFKI’)/Orders?$filter=Active
eq true&$orderby=OrderDate
Paging over results is achieved by combining the parameters
“skip” and “top”; for example, if listing 10 customers per page, the third page
would be obtained with this URI:
http://myserver/data.svc/Customers?$skip=30&$top=10
For URIs that represent a specific entity instance or a
specific association, not only is it possible to use HTTP GET to retrieve the
entity, but the system also handles HTTP PUT to replace an entity (effectively
updating the values in the data service), HTTP POST to create new entities and
HTTP DELETE to eliminate entities from the data service and its underlying
store. Additional actions are also
supported and are detailed in the “Using ADO.NET Data
Services” whitepaper.
>Format Independent
ADO.NET Data Services uses minimalistic formats to represent
data, and supports more than one format to accommodate as many client agents as
possible.
Currently ADO.NET Data Services can represent data in JSON
(JavaScript Object Notation) and AtomPub (Atom Publishing Protocol) formats.
The default representation format is AtomPub and is
generated using a fixed mapping of the entity structure to the XML elements in
AtomPub with some added semantics to make use of the information provided by
the EDM schema. For example, the customer URI from the example above was:
http://myserver/data.svc/Customers(‘ALFKI’)
If an HTTP GET request is sent to the URI above, the data
service would respond with a payload similar to that shown below:
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<entry xml:base="http://myserver/data.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://myserver/data.svc/Customers('ALFKI')</id>
<updated>2008-08-31T21:36:21Z</updated>
<title />
<author>
<name />
</author>
<category
term="NorthwindModel.Customers"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"
/>
<content
type="application/xml">
<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>
</content>
<link rel="edit" href="Customers('ALFKI')" title="Customers" />
<link
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Orders" title="Orders" href="Customers('ALFKI')/Orders" type="application/xml;type=feed"
/>
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/CustomerDemographics" title="CustomerDemographics"
href="Customers('ALFKI')/CustomerDemographics"
type="application/xml;type=feed"
/>
</entry>
NOTE: The Customer entity
in the example above contains both properties with primitive values such as
"CompanyName" and properties that point to another resource such as
"Orders".
The client agent can choose another format by simply
changing the Accept header in the HTTP request (see HTTP RFC 2616 content type
negotiation sections for details). For example, by setting the Accept header to
"application/json" the client agent would obtain the following result
in JSON format instead of the AtomPub format shown above:
{
“d”:{
__metadata: {
uri:
"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:
"Customers(\'ALFKI\')/Orders"
}
}
CustomerDemographics: {
__deferred: {
uri:
"Customers(\'ALFKI\')/CustomerDemographics"
}
}
}
}
While semantically equivalent, this version will integrate
naturally and easily with JavaScript environments such as AJAX-based
applications.
NOTE: The payload is
contained within the fixed “d” property to mitigate unintended use of JSON
payloads created by an ADO.NET Data Service.
>Uniform patterns enable reuse
The application of simple and clear semantics from the data
model to URIs and payloads in various formats result in uniform patterns for
interacting with data services.
These uniform patterns present the opportunity to create
reusable components that build on top of the patterns and the semantics behind
them and provide powerful abstractions for developers.
For example, an AJAX-enabled “grid” or “table” widget could
take “/Customers” as its input, and would be able to list customer entities,
show the list in pages, allow the user to filter based on simple criteria and
to click on different columns to change the sort order. I would also be able to
support in-place editing, creation and deletion of customer rows.
In addition to UI widgets, client-side libraries can be
built to present higher-level programming interfaces to the data services.
Again, the use of uniform patterns allows these libraries to surface very
expressive constructs such as association traversal. The ADO.NET Data Services
framework includes a client library for the .NET Framework and Silverlight that
can expose a data service in terms of .NET objects and supports association
traversal, identity resolution and other simple, yet powerful data programming
constructs. In addition, the ADO.NET
Data Services Framework includes a JavaScript library (available on
www.codeplex.com) to simplify inserting, updating and deleting resources from
AJAX applications.
>A good web citizen
ADO.NET Data Services are designed to leverage and integrate
well with the web in various ways. The use of a REST-style interface with URIs
to point to resources and content-types, negotiated through HTTP headers, are
examples of this.
Going beyond that, ADO.NET Data Services has being designed
to leverage other aspects such as the well-established HTTP caching
infrastructure.
The ADO.NET Data Services framework also enables use of the
regular authentication schemes supported by the execution environment that
hosts a particular data service (typically Microsoft ASP.NET & Windows
Communication Foundation).
>Storage Independence
ADO.NET Data Services are intended to expose data regardless
of the underlying data source. In
general, ADO.NET Data Services work by accepting an HTTP request for a resource
identified by a URI, deserializing the request and then passing a
representation of that request to an ADO.NET Data Service provider to execute
the request on the underlying data store. This separation of the ADO.NET Data
Services protocol (URI, payload format and interaction model) from the data store
enables ADO.NET Data Services to expose data at the level of abstraction
presented to it by the provider, which may be different from the schema of the underlying
store. ADO.NET Data Services enables
easily creating data services using:
- Entity Framework data sources, which expose a
conceptual model of data stored in relational databases (SQL, Oracle, DB2,
MySql, etc)
- Any
+
(interface to support update semantics for ADO.NET Data Services)
implementation.This enables ADO.NET
Data Services to expose any data store as REST-based endpoints regardless of
its storage mechanism.
>Incorporating Business Logic
The sections above described how ADO.NET Data Services map a
URI to each entity within an EDM-based conceptual model, which a developer
defines and provides to a data service to expose as HTTP resources. This
approach works well for classes of data (ex. blog articles) that can be served
from the storage medium all the way to the application where it is likely to be
directly presented to the end user. While such data exists, it is clear that
not all data fits this mold. Some types of data for a variety of reasons (such
as validation etc.) need to be accompanied by business logic that governs how
the data is served.
ADO.NET Data Services enables a developer to go beyond
simple REST semantics, where required, by defining service operations. Service
operations enable a data service to define a method on the server, which just
like all other ADO.NET Data Service resources, is identified by a URI. For
example, the URI http://myserver/data.svc/MyFavoriteBooks?category=sports&$orderby=Title&$top=2&$skip=2
represents a call to the service operation named MyFavoriteBooks that takes a
single category parameter. One of the interesting features of service
operations is that the output of the service operation can be acted on using
standard ADO.NET Data query string operators.
This is shown in the query string of the example above where the
$orderby, $top and $skip operators are applied to the results of the operation
to sort and then page over the result set.
Another mechanism, known as Interceptors is also provided,
which enables custom validation or access control logic to be plugged into the
request/response processing pipeline of a data service. Interceptors allow an
arbitrary method to be called when a particular CRUD action occurs on a given
resource being exposed by ADO.NET Data Services. Such a method may then alter
the data, restrict access based on the principle of the request or even
terminate the operation.
Finally, a significant amount of access to relational
databases occurs via stored procedures.
ADO.NET Data Services supports the use of stored procedures in two ways:
- ADO.NET Data Services supports the Entity
Framework as a data source, which makes it simple to map entities exposed
through a data service to stored procedures within a relational database
- Service operations can be used to map stored
procedures to entities exposed via an ADO.NET Data Service
>Metadata for REST Services
ADO.NET Data Services are simply resource stores with added
semantics that reflect on the payloads and URIs. Like WSDL for SOAP-based web
services, an ADO.NET Data Service is described using an Entity Data Model (EDM)
conceptual schema. An EDM conceptual
schema (http://msdn2.microsoft.com/en-us/library/bb399281(VS.90).aspx)
is an XML document written using the conceptual schema definition language
(CSDL) which describes entities and the associations among those entities. This description language was selected
because its constructs enable rich expressivity for resource-based services and
no equivalent language currently exists in the web community for REST
services.
By virtue of having a well defined description mechanism for
ADO.NET Data Services, opportunities in the tooling space exist where
design-time tools can extract metadata from services and build front-ends,
wrappers and metadata repositories used at runtime to facilitate consumptions
of these services.
The following example CSDL document defines two entities:
Customers and Orders, each with a few primitive type properties as well as a
special “navigation property” which defines the association between the
entities. In the example a 1-to-many
association exists between Customer and Order entities. Valid URIs for a data service described using
the CSDL below are:
All Customers: http://host/service.svc/Customers
All Orders, ordered by ship date: http://host/service.svc/Orders?$orderby=ShippedDate
Customer with key “ALFKI”: http://host/service.svc/Customers(‘ALFKI’)
Orders for the Customer with key “ALFKI”: http://host/service.svc/Customers(‘ALFKI’)/Orders
The metadata document for the service: http://host/service.svc/$metadata
<?xml
version="1.0" encoding="utf-8" standalone="yes"
?>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices>
<Schema
Namespace="NorthwindModelApp"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer
Name="NorthwindEntities"
m:IsDefaultEntityContainer="true">
<EntitySet
Name="Customers" EntityType="NorthwindModel.Customers"
/>
<EntitySet
Name="Orders" EntityType="NorthwindModel.Orders" />
<AssociationSet
Name="Orders_Customers"
Association="NorthwindModel.Orders_Customers">
<End Role="Customers"
EntitySet="Customers" />
<End
Role="Orders" EntitySet="Orders" />
</AssociationSet>
</EntityContainer>
</Schema>
<Schema
Namespace="NorthwindModel"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityType
Name="Order">
<Key>
<PropertyRef
Name="OrderID" />
</Key>
<Property
Name="OrderID" Type="Edm.Int32" Nullable="false"
/>
<Property Name="ShippedDate"
Type="Edm.DateTime" Nullable="true"
DateTimeKind="Unspecified" PreserveSeconds="true"
/>
<Property
Name="ShipAddress" Type="Edm.String"
Nullable="true"
MaxLength="60"
Unicode="true" FixedLength="false" />
<NavigationProperty
Name="Customers"
Relationship="NorthwindModel.Orders_Customers"
FromRole="Order"
ToRole="Customer" />
</EntityType>
<EntityType
Name="Customer">
<Key>
<PropertyRef
Name="CustomerID" />
</Key>
<Property
Name="CustomerID" Type="Edm.String"
Nullable="false"
MaxLength="5"
Unicode="true" FixedLength="true" />
<Property
Name="CompanyName" Type="Edm.String"
Nullable="false"
MaxLength="40"
Unicode="true" FixedLength="false" />
<Property
Name="Address" Type="Edm.String" Nullable="true"
MaxLength="60"
Unicode="true" FixedLength="false" />
<NavigationProperty
Name="Orders"
Relationship="NorthwindModel.Orders_Customers"
FromRole="Customer"
ToRole="Order" />
</EntityType>
<Association
Name="Orders_Customers">
<End Role="Customer"
Type="NorthwindModel.Customers"
Multiplicity="0..1"
/>
<End Role="Order"
Type="NorthwindModel.Orders" Multiplicity="*" />
</Association>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
>Conclusion
Separation of presentation and data is an intrinsic
characteristic of new web applications. This separation introduces the need and
opportunity for data services for the web.
The ADO.NET Data Services framework is Microsoft’s
technology for creating and consuming data services. These data services use
the Entity Data Model (EDM) to model data in terms of entities. These entities
are exposed as URI-addressable resources that can be accessed using standard
HTTP requests. ADO.NET Data Services
also establishes uniform patterns that UI widget frameworks and libraries can
leverage to provide additional services on top of the data services.