In this chapter we will cover:
Building an OData service via WCF Data Service and ADO.NET Entity Framework
Building an OData service with WCF Data Service and LINQ to SQL
Exposing OData endpoints from WCF RIA Service
Adding custom operations on OData service
Exposing database stored procedures in WCF Data Service
Using custom data objects as the data source of WCF Data Service
Using Interceptors to customize a WCF Data Service
Accessing ASP.NET context data in WCF Data Service
Creating a custom WCF Data Service provider
Open Data Protocol (OData) is a web protocol for querying and updating data, which can be freely incorporated in various kinds of data access applications. OData makes itself quite simple and flexible to use by applying and building upon existing well-defined technologies, such as HTTP, XML, AtomPub, and JSON.
WCF Data Service is the main component for building OData service on .NET Framework platform. WCF Data Service supports exposing various data source models such as ADO.NET Entity Framework, LINQ to SQL, and CLR Objects through OData service endpoints. Also, we're not limited to these existing data models, we can build our own custom Data Service Provider or convert other services (such as WCF RIA service) to OData service. In this chapter, we will demonstrate several cases of using WCF Data Service to build OData services that can deal with different kinds of data source models.
Open Data Protocol (OData) is a web protocol for querying and updating data, which can be freely incorporated in various kinds of data access applications. OData makes itself quite simple and flexible to use by applying and building upon existing well-defined technologies, such as HTTP, XML, AtomPub, and JSON.
WCF Data Service is the main component for building OData service on .NET Framework platform. WCF Data Service supports exposing various data source models such as ADO.NET Entity Framework, LINQ to SQL, and CLR Objects through OData service endpoints. Also, we're not limited to these existing data models, we can build our own custom Data Service Provider or convert other services (such as WCF RIA service) to OData service. In this chapter, we will demonstrate several cases of using WCF Data Service to build OData services that can deal with different kinds of data source models.
There are various means to create an OData service on the .NET Framework platform. And by using different means, we might need to choose different kind of data sources to provide the actual data that will be published and exposed in the OData service. In this recipe, we will start from one of the most typical approaches—creating an OData service through WCF Data Service and ADO.NET Entity Framework data model.
As we will use ADO.NET Entity Framework as the data source of our OData service, make sure you have a sample database, such as Northwind, installed in a local SQL Server instance. You can use SQL Express instance (the free version of SQL Server) for convenience.
The source code for this recipe can be found in the \ch01\ODataEFServiceSln\
directory.
To concentrate on the OData service generation and make the progress simple and clear, we will use an empty ASP.NET web application with a single OData service for demonstration. The detailed steps are as follows:
1. Launch Visual Studio 2010 IDE.
2. Fire the New Project menu and create an ASP.NET Empty Web Application through the Add New Project wizard (see the following screenshot).
3. Use the Project | Add New Item context menu to add a new ADO.NET Entity Data Model (see the following screenshot).
The wizard will guide you on selecting a source database (such as the Northwind database used in this case) .The following screenshot shows the entity classes generated through the Northwind sample database:
4. Create a new OData service via the WCF Data Service item template.
The WCF Data Service item template can be found in the Visual Studio 2010 built-in template list (see the following screenshot).
By clicking on the Add button, Visual Studio will automatically generate the
.svc
file and its associated code files for the WCF Data Service item.5. Use View Code context menu to open the source file of the generated WCF Data Service and replace the default service type (the generic parameter) with the Entity Framework model class (generated in the previous step).
The following code snippet shows the WCF Data Service, which uses the Northwind data model class in this sample:
namespace ODataEFService { public class NWDataService : DataService< ODataEFService. NorthwindEntities > { public static void InitializeService(DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.SetEntitySetAccessRule ("*", EntitySetRights.All); } } }
Note
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
6. Now, we can start running the service by selecting the
.svc
file in Solution Explorer and choose the View in browser context menu.The default page of the WCF Data service will display all the OData entities that have been exposed in the service (see the following screenshot).
In our sample web project, there are only two items. One is the ADO.NET Entity Framework data model and the other is the WCF Data Service item (as shown in the following project structure screenshot).
![]() |
WCF Data Service has helped encapsulate all the underlying details of implementing an OData service. When using WCF Data Service to generate OData service, what we need to do is:
Prepare the data source provider type (in our case, the ADO.NET Entity Framework model)
Associate the data source provider with the WCF Data Service
Also, as the name indicates, WCF Data Service is a special implementation of WCF service. And more specifically, WCF Data Service is a specially implemented WCF service over the REST HTTP endpoint (by using the WebHttpBinding
binding type). In most cases, we do not need to take care of those WCF service-specific configuration details (in web.config
file). If we open the web.config
file of our sample service, we can find that there is almost nothing defined within the<system.serviceModel>
element for the WCF configuration (see the following screenshot).
![]() |
In addition to ADO.NET Entity Framework, LINQ to SQL is another popular and powerful component we can use for mapping relational database objects to .NET CLR class objects. Many popular RDBMS (such as SQL Server and Oracle) have provided LINQ to SQL providers. And for WCF Data Service, it is quite reasonable to add support for exposing a LINQ to SQL based data source via OData service endpoints. In this recipe, we will introduce you to creating an OData service from a LINQ to SQL based data source model.
Make sure you have a sample database, such as Northwind, installed in a local SQL Server instance. You can use an SQL Express instance (the free version of SQL Server) for convenience.
The source code for this recipe can be found in the \ch01\ODataLINQ2SQLServiceSln\
directory.
You can follow the steps given for creating an OData service from LINQ to SQL data entities:
1. Create a new ASP.NET Empty Web Application in Visual Studio 2010.
2. Create the LINQ to SQL data model types by using the LINQ to SQL Classes item template (see the following screenshot).
After the data model is created, we can use Visual Studio Server Explorer to drag certain tables (from the sample database) into the data model designer. This will make the Visual Studio IDE create the corresponding data entity types.
3. Create a new WCF Data Service based on the generated LINQ to SQL data model.
This time, we use the LINQ to SQL data model class as the generic parameter of the service class (see the following code snippet).
public class NWODataService : DataService< ODataLINQ2SQLService.NorthwindDataContext > { // This method is called only once to initialize service-wide // policies. public static void InitializeService(DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.SetEntitySetAccessRule("*", EntitySetRights.All); } }
4. Select the
.svc
service file in Visual Studio and launch it through the View in browser context menu.
Although we directly use the LINQ to SQL data model class as the data source, the WCF Data Service runtime actually treats the LINQ to SQL data model class like a custom data source type. Therefore, any public member (of the data model class) who implements the IQueryable
interface will be exposed as an entity set in the generated service. We will talk more about using custom data source type for WCF Data Service within the Using custom data objects as the data source of WCF Data Service recipe of this chapter.
By default, the WCF Data Service, which uses the LINQ to SQL data model class, does not support editing/updating operations. In order to make the LINQ to SQL based WCF Data Service support editing/updating, we need to implement the IUpdatable
interface (under System.Data.Services namespace)
on the LINQ to SQL data model class (see the following code snippet).
partial class NorthwindDataContext: IUpdatable
{
......
}
For detailed information about implementing IUpdatable
interface for LINQ to SQL data model class, you can refer to the following MSDN reference:
How to: Create a Data Service Using a LINQ to SQL Data Source (WCF Data Services) available at http://msdn.microsoft.com/en-us/library/ee373841.aspx
WCF RIA Service is one of the great extension components based on the standard WCF service. WCF RIA Service is designed for building data access services (for n-tier solutions), which will not only expose data sets to clients but also encapsulate most of the business/application logics at service layer. With the latest WCF RIA Service version, we can make a WCF RIA Service expose data through various kinds of endpoints such as SOAP, OData, and JSON.
In this recipe, we will show you how to open an OData endpoint from an existing WCF RIA Service.
To play with WCF RIA Service, we need to install Visual Studio 2010 Service Pack 1, which includes the runtime and development tools for WCF RIA Service V1 SP1.
Visual Studio 2010 Service Pack 1 is available at http://support.microsoft.com/kb/983509.
The source code for this recipe can be found in the \ch01\ODataRIAServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Create the ADO.NET Entity Framework data model from the sample database.
The following screenshot shows the class diagram of the data model created from the Northwind sample database (four tables are included):
3. Create a new WCF RIA Service by using the Domain Service Class item template in Visual Studio (see the following screenshot).
4. Specify the service options (especially the one for enabling an OData endpoint) in the Add New Domain Service Class dialog (see the following screenshot).
The following are all the options we need to set for a new WCF RIA Service:
Domain Service Class name: This is the type name of our RIA service class.
Available DataContext/ObjectContext classes: This is the data model class we will use for providing the underlying data objects. Make sure we have saved all items in the project so that the ADO.NET Entity Framework data model class will appear in the drop-down list.
Enable client access and Expose OData endpoint options: As the name explains, these two options will enable the RIA service to be accessed from client applications and also add an additional endpoint on it so as to expose data entities in an OData compatible format.
5. Create a
.svc
file as the service access endpoint for the WCF RIA Service.In the
.svc
file, we need to specify theServiceHostFactory
andService
types through the@ServiceHost
directive (see the following code snippet).<%@ ServiceHost Language="C#" Debug="true" Service="ODataRIAService.NWDomainService" Factory="System.ServiceModel.DomainServices.Hosting.DomainServiceHostFactory, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
As shown in the previous
@ServiceHost
directive, we need to supply the full name (including namespace and assembly name) of theServiceHostFactory
type in theFactory
attribute.Note
If you use the WCF service item template to create a new
.svc
file, Visual Studio will generate theServiceContract
andService
implementation code files automatically. To prevent this, you can create a Text or XML file instead and manually change the file extension to.svc
(and adjust the file content correspondingly).6. Launch the WCF RIA Service and access its OData endpoint by adding the odata/ suffix to the URL.
By adding the odata/ suffix to the URL over the base service address, we can reach the OData endpoint exposed by the WCF RIA Service. The default output of the OData endpoint is just the same as a standard WCF Data Service (see the following screenshot).
![]() |
When creating the sample WCF RIA Service, we enable the OData endpoint on it by selecting the Expose OData endpoint option in the Add New Domain Service Class dialog. Actually, we can find the magic behind the dialog within the web.config
file (see the following configuration fragment).
![]() |
The dialog adds a domainServices/endpoints/add
element in the<system.serviceModel>
section. This element tells the runtime to add a new endpoint for each WCF RIA Service and this endpoint will generate an OData format response (by using the System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory
type).
Likewise, if you have some existing WCF RIA Services, which were created without the OData endpoints enabled, we can simply make them OData enabled by adding the previous configuration settings manually in the web.config
file.
By default, WCF Data Service will expose all data object collections provided by the data source in the format of OData entity sets. In addition to this, we can also add custom methods/operations on a given WCF Data Service. By using such custom operations, we can further extend our OData services so as to expose additional data in arbitrary formats, such as XML, JSON, and Binary.
In this recipe, we will demonstrate how to add custom operations to a WCF Data Service.
This sample case still uses the same ADO.NET Entity Framework based WCF Data Service like what we've discussed in the previous recipes. We will add some custom operations to it so as to expose additional data to client.
The source code for this recipe can be found in the \ch01\CustomOperationServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Create an ADO.NET Entity Framework based WCF Data Service through the Northwind sample database.
3. Add custom operations into the WCF Data Service class.
We will add two operations here, one for retrieving the current time on service server (return
DateTime
value) and another for retrieving some test data entities of theCategory
entity type (see the following code snippet).public class NWDataService : DataService<CustomOperationService.NorthwindEntities> { ...... [WebGet] public DateTime GetServerTime() { return DateTime.Now; } [WebGet] public IQueryable<Category> GetDummyCategories() { var cates = new List<Category>(); cates.Add(new Category() { CategoryID = 1, CategoryName = "Category 1", Description = "Desc of Category 1" }); cates.Add(new Category() { CategoryID = 2, CategoryName = "Category 2", Description = "Desc of Category 2" }); cates.Add(new Category() { CategoryID = 3, CategoryName = "Category 3", Description = "Desc of Category 3" }); return cates.AsQueryable(); } }
As the shown in the previous code, both operation functions need to be public and non-static member methods of the service class.
4. Enable the operation access rules in the service initialization code (see the following code snippet).
public static void InitializeService(DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.SetEntitySetAccessRule("*", EntitySetRights.All); config.SetServiceOperationAccessRule("*", ServiceOperationRights.All); }
5. Select the
.svc
file to launch the service and directly invoke the custom operations (by typing the operation address) in the web browser.The following screenshot shows the web browser window after invoking the
GetServerTime
operation:The following is the output obtained by invoking the
GetDummyCategories
operation:
As shown in the previous sample code, we can add custom operations to WCF Data Service in the same way as we add service operations to a standard WCF REST service. Also, the WebGetAttribute
attribute over each sample operation indicates that the operation can be accessed through the HTTP GET method (the expected method for operations that return data). We can also apply the WebInvokeAttribute
attribute so as to make the operation support other HTTP methods.
Also, in order to allow clients to invoke custom operations, we need to grant the access rules in the InitializeService
function just like we do for entity sets exposed in WCF Data Service.
For more information about access rules and permission configuration on WCF Data Service, see Chapter 7, Working with Security.
When developing data access applications with relational databases, we will often use stored procedures to encapsulate some frequently used queries. We can also gain performance improvements by using stored procedures (compared to using raw SQL queries).
Then, can we also take advantages of database stored procedures in our WCF Data Services which expose data from relational database? Absolutely yes! In this recipe, we will discuss how to expose data entities from relational database via stored procedures.
The service here will expose two stored procedures from the Northwind database. They are the CustOrdersOrders
procedure (return Order list of a given customer) and the Ten Most Expensive Products
procedure. The following are the raw signatures of these two stored procedures:
ALTER PROCEDURE [dbo].[Ten Most Expensive Products] AS ... ALTER PROCEDURE [dbo].[CustOrdersOrders] @CustomerID nchar(5) AS ...
The source code for this recipe can be found in the \ch01\ODataSPServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Create the ADO.NET Entity Framework data model and include the stored procedures together with the tables.
We can select the database tables, views, and stored procedures we want in the Choose Your Database Objects dialog (see the following screenshot). In this case, we need to select all tables and two stored procedures.
3. Add Function Import for the stored procedures in the Data Model class.
4. Open the EF designer by double-clicking on the generated data model
(.edmx
file in Visual Studio Solution Explorer).5. Right-click on the designer surface and fire the Function Import... context menu (see the following screenshot).
6. In the Add Function Import dialog, specify the detailed information of the target stored procedure we want to import.
The following screenshot shows the import settings for the CustOrdersOrders procedure:
The return value of the previous stored procedure mapping function is a custom complex object. You can create this complex data type (based on the columns returned in the stored procedure) by using the Create New Complex Type button at the bottom of Add Function Import dialog (see the following screenshot).
7. Add custom operations in the WCF Data Service class, which directly invokes the stored procedure mapping functions imported in the previous step.
The following code snippet shows the custom operations definition in the sample WCF Data Service:
[WebGet] public IQueryable<ExpProductObj> GetTop10ExpensiveProducts() { return this.CurrentDataSource. GetTop10ExpensiveProducts().AsQueryable(); } [WebGet] public IQueryable<CustomerOrderObj> GetOrdersByCustomer(string custID) { return this.CurrentDataSource. GetOrdersByCustomer(custID).AsQueryable(); }
8. Launch the service and invoke the stored procedure based operations in the web browser.
The following screenshot shows the web browser output by invoking the
GetOrdersByCustomer
operation in the sample service:
To use stored procedures in WCF Data Service (using the ADO.NET Entity Framework data model as data source), we need to import stored procedures as functions in the generated EF data model class. In this sample, we create some custom data types for the return value of each stored procedure mapping function. This is because in most cases, the returned data columns from a given stored procedure don't exactly match a complete data entity type (corresponding to the target database table).
In addition to the imported functions on EF data model class, we also need to add custom operations within the WCF Data Service class. These operations simply delegate the operation call to the corresponding stored procedure mapping functions.
When calling a service operation mapping to a void stored procedure (which does not return any value), we can simply use the URL address of the operation (relative from the service base address). For stored procedures that take some input parameters, we can supply the parameters by using query strings in the operation URL (as shown in the previous GetOrdersByCustomer
operation sample).
So far we've explored several examples, which use relational database objects as the data provider (through Entity Framework, LINQ to SQL, or custom operations). However, we're definitely not limited to these data sources; WCF Data Service provides the flexibility for developers to use custom CLR objects as data sources.
In this recipe, we will see how to use custom data objects as a WCF Data Service data source and expose OData entitiy sets based on the data members of the custom data objects.
In this recipe, we will create a WCF Data Service for exposing some books and book categories information to clients. Instead of using ADO.NET Entity Framework or LINQ to SQL, we will define some custom CLR types to represent the data model of the sample service.
The source code for this recipe can be found in the \ch01\CLRObjDataServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Create custom CLR types to represent the book and book category items.
The following code snippet shows the definition of the sample CLR types:
namespace CLRObjDataService { [DataServiceKey("ISBN")] [DataServiceEntity] public class BookInfo { public string ISBN { get; set; } public string Title { get; set; } public string Author { get; set; } public DateTime PubDate { get; set; } public BookCategory Category { get; set; } } [DataServiceKey("Name")] [DataServiceEntity] public class BookCategory { public string Name { get; set; } public List<BookInfo> Books { get; set; } } }
3. Create a data context type that acts as a container for entity sets based on the custom CLR types (defined in the previous step).
The following is the code of the sample data context type (see the following
BookServiceContext
class), which exposes two entity sets based on theBookInfo
andBookCategory
x classes:public class BookServiceContext { static IList<BookCategory> _categories = null; static IList<BookInfo> _books = null; public IQueryable<BookCategory> BookCategories { get { return _categories.AsQueryable(); } } public IQueryable<BookInfo> Books { get { return _books.AsQueryable(); } } }
For demonstration, we have also defined a static constructor for generating some test data (see the following code snippet).
static BookServiceContext() { _books = new List<BookInfo>(); _categories = new List<BookCategory>(); for(int i=1;i<=3;++ i) { var cate = new BookCategory() { Name = "Category_" + i.ToString() }; cate.Books = new List<BookInfo>(); for (int j = 1; j <= 3; ++j) { int bid = (i*10+j); var book = new BookInfo() { ISBN = "ISBN" + bid.ToString(), Title = "Book Title " + bid.ToString(), Author = "Author", PubDate = DateTime.Now, Category = cate }; _books.Add(book); cate.Books.Add(book); } _categories.Add(cate); } }
4. Create a new WCF Data Service and use the custom data context type as its data source.
The following code snippet shows the sample
BookDataService
class, which uses theBookServiceContext
class (created in previous step) as the data source parameter:public class BookDataService : DataService< BookServiceContext > { public static void InitializeService (DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.SetEntitySetAccessRule("*", EntitySetRights.All); } }
Like the ADO.NET Entity Framework-based WCF Data Service, we also need to set the proper entity set access rules in the initialization function.
5. Launch the service and view the custom data entity sets in the web browser.
For the sample service, we can access the exposed entity sets at the following locations:
Book category entity set
(http://[server]:[port]/BookDataService.svc/BookCategories)
Book entity set
(http://[server]:[port]/BookDataService.svc/Books)
We can also use the following URL to retrieve book entities that belong to a certain category entity:
http://[server]:[port]/BookDataService.svc/BookCategories('Category_1')/Books
The following screenshot shows the book entities that belong to the first category entity:
![]() |
Now, let's take a look at what makes these things work. As we can see, each entity set we expose in the sample service is coming from its corresponding member property defined in the data context type. Such member properties should be declared as IQueryable<Entity Type>
type so that the WCF Data Service runtime can correctly locate them and expose them as entity sets in the service.
Note
For a given entity type T
, we can only define one member property (on the data context class), which returns IQueryable<T>
. In other words, we cannot expose multiple entity sets using the same entity type.
For each custom entity type, we must specify a key property by using the DataServiceKeyAttribute
attribute. This key property is used for identifying entity instances in a given entity set (just like the primary key for the relational table).
The BookCategory
entity type has a Books
property of the List<BookInfo>
type. Such kind of entity collection properties will be automatically treated as Navigation properties on the target entity type. For OData clients, they can use these Navigation properties (by using relative URI address) to retrieve the associated subentities from the primary entity instance (see the previous sample code).
We have discussed LINQ to SQL based data sources in the previous recipe. Actually, LINQ to SQL is a special case of a custom data object based data source, since the LINQ to SQL data model has already done most of the work for us. If you are interested in finding out more about building WCF Data Service data source with a custom CLR type, you can refer to the following MSDN reference:
Reflection Provider (WCF Data Services) available at http://msdn.microsoft.com/en-us/library/dd723653.aspx
If you've been familiar with standard WCF service programming, you probably have been playing with the Message Inspectors, which are one of the WCF extension components for intercepting the request and response messages of service operation calls.
Well, for WCF Data Service, we also have the similar extension component called Interceptors, which can help intercepting the service requests issued from client callers.
By using WCF Data Service Interceptors, we can customize the code logic of certain operations against a given entity set. In this recipe, we will see how to do some customization on the data processing code logic in WCF Data Service by using custom Interceptors.
In this recipe we will build a WCF Data Service based on the Northwind EF data model. The service will expose two data entity sets, one is from the Categories
table, and the other is from the Products
table. For demonstration, we will add two custom Interceptors against these two entity sets so as to change their query and delete behavior.
The source code for this recipe can be found in the \ch01\QIDataServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Create a WCF Data Service with ADO.NET Entity Framework data model (using the Northwind database).
The service will only expose the
Categories
andProducts
entity sets from the data source (see the following screenshot).3. Add custom Interceptors in the WCF Data Service class and bind them with the target entity sets.
There are two Interceptors to define here. The first one is a QueryInterceptor against the
Products
entity set. It will restrict the query result so as to expose Product entities that haveUnitsInStock > 0
. The second one is a ChangeInterceptor against theCategories
entity set. By using it, no delete operation is allowed on theCategories
entity set. The following code snippet shows the WCF Data Service class, which includes both Interceptors:public class NWDataService : DataService< NorthwindEntities > { public static void InitializeService(DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.SetEntitySetAccessRule("*", EntitySetRights.All); } // Query Interceptor for Products entity set [QueryInterceptor("Products")] public Expression<Func<Product, bool>> onQueryProducts() { // Only return products that have units in stock return p => p.UnitsInStock > 0; } // Change Interceptor for Categories entity set [ChangeInterceptor("Categories")] public void onChangeCategories (Category cate, UpdateOperations operations) { if (operations == UpdateOperations.Delete) { throw new DataServiceException(400, "Delete operation is not supported on Categories entity set."); } } }
4. Launch the service and try accessing the entity sets, which have Interceptors applied.
By accessing the
Products
entity set, we can find that all the entities returned by it have theUnitsInStock
field greater than zero. Also, if we explicitly use query filter to look forProduct
entities that haveUnitsInStock
equal to zero, we will get empty results (see the following screenshot).
In the sample service, we have applied a QueryInterceptor on the Products
entity set. Actually, a QueryInterceptor
is just a function, which returns a Lambda expression with the following signature:
Func<[Entity Type], bool>
Then, why does it use an expression instead of a delegate function directly? The reason is that by using an expression, it is more convenient for the underlying WCF Data Service runtime to forward such QueryInterceptor
injected code logic to the actual query provider (such as the ADO.NET Entity Framework provider, which will generate T-SQL based on the query) that will fetch the data from the backend data source.
QueryInterceptor will be invoked when HTTP GET based query requests are received against the target entity set; while ChangeInterceptor will be invoked when update/modify operations are called. In this sample, our onChangeCategories
Interceptor will check the incoming request to see if it is a delete operation against the Categories
entity set. If the checking result is true
, a DataServiceException
will be thrown out. In a real-world case, we can apply more complicated code logic to change the default update/modify behavior against the target entity sets.
For more information about using Interceptors in WCF Data Service, you can read the following MSDN reference:
Interceptors (WCF Data Services) available at http://msdn.microsoft.com/en-us/library/dd744842.aspx
OData protocol is naturally based on HTTP and other web standards. OData services built with WCF Data Service are often hosted in an ASP.NET web application. Therefore, it is quite possible that a WCF Data Service is deployed side-by-side with many other web resources, such as ASP.NET web pages, ASMX web services, and HTTP handlers. ASP.NET web pages can access many ASP.NET runtime specific context data such as session states, client user info, application states, cache, and HTTP request headers. Is this also possible for WCF Data Services (hosted in ASP.NET web applications)?
Well, this can be easily achieved with the current WCF Data Service programming model. In this recipe, we will introduce how to access ASP.NET context data in WCF Data Service code.
In this recipe, we will create a WCF Data Service, which will expose the session states and HTTP client user headers (of the ASP.NET host web application) as entity sets to the service callers. Also, to make the service code simple and clear, we will use custom CLR types instead of a ADO.NET Entity Framework data model as the service data source.
The source code for this recipe can be found in the \ch01\WebContextDataServiceSln\
directory.
1. Create a new ASP.NET Empty Web Application.
2. Define the custom classes, which will be used as entity types and data context type of the sample WCF Data Service.
The following is the complete definition of the
ClientInfoEntity
andSessionItemEntity
entity types in this sample:[DataServiceEntity] [DataServiceKey("ID")] public class ClientInfoEntity { public int ID { get; set; } public string IPAddress { get; set; } public string UserAgent { get; set; } public bool Authenticated { get; set; } } [DataServiceEntity] [DataServiceKey("KeyName")] public class SessionItemEntity { public string KeyName { get; set; } public string TypeName { get; set; } }
The following
WebContextEntityContainer
class is used as the service data context (the container of the two sample entity sets):public class WebContextEntityContainer { public IQueryable<SessionItemEntity> SessionItems { get { var items = new List<SessionItemEntity>(); foreach (string key in HttpContext.Current.Session.Keys) { var item = new SessionItemEntity() { KeyName = key, TypeName = HttpContext.Current.Session[key]. GetType().FullName }; items.Add(item); } return items.AsQueryable(); } } public IQueryable<ClientInfoEntity> ClientInfos { get { var req = HttpContext.Current.Request; var clientInfo = new ClientInfoEntity() { ID = 1, Authenticated = req.IsAuthenticated, IPAddress = req.UserHostAddress, UserAgent = req.UserAgent }; return new ClientInfoEntity[]{clientInfo}.AsQueryable(); } } }
3. Create a new WCF Data Service and use the
WebContextEntityContainer
class as a data source (see the following code snippet).public class ContextInfoDataService : DataService< WebContextEntityContainer > { .. }
4. Launch the service in the web browser and query the two entity sets, which return data specific to the current ASP.NET context.
We can access the
SessionItems
andClientInfos
entity sets through the following URI addresses:http://[server]:[port]/ContextInfoDataService.svc/SessionItems
http://[server]:[port]/ContextInfoDataService.svc/ClientInfos
The following screenshot shows the query output of the first (also the only) entity instance from the
ClientInfos
entity set:
![]() |
The WCF Service programming model provides built-in support for service code to access ASP.NET context data in case the service is hosted in an ASP.NET web application. Since WCF Data Service is a special implementation of WCF Service, accessing ASP.NET context data in WCF Data Service code is naturally supported too. Actually, whenever a new WCF Data Service is created in Visual Studio (within an ASP.NET web project), the IDE will automatically enable the ASP.NET Compatibility mode (see the following screenshot) in the web.config
file, which is necessary for WCF Service (also WCF Data Service) to access ASP.NET context data of the hosting web application.
![]() |
For demonstration purposes, our sample ASP.NET web application also contains a simple ASP.NET web page, which will help in generating some test session states data (see the following InitSession
page class).
public partial class InitSession : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (Session.Count == 0) { Session.Add("string item", "some text"); Session.Add("int item", 120); Session.Add("boolean item", true); Session.Add("date item", DateTime.Now); Session.Add("array item", new int[]{1,2,3}); } } }
So far we've explored various ways to build an OData service with .NET Framework platform including WCF Data Service with ADO.NET Entity Framework, LINQ to SQL, custom CLR objects, and WCF RIA service.
However, what if we want to expose some custom data through OData endpoints but none of the above means can help? Such conditions do exist, for example, we might have some data that is not of relational database structure, or the data object types are previously defined, which haven't applied those WCF Data Service specific attributes (necessary for using custom CLR objects based data source).
Don't worry, the WCF Data Service framework has already provided a powerful extension model, which can let you create a custom provider in order to expose arbitrary format custom data in a WCF Data Service. In this recipe, we will see how to create a custom WCF Data Service provider and use it to expose some custom data.
In this recipe, we will choose filesystem as an example and build a WCF Data Service, which exposes the information of all files within a given directory. Also, we will create several custom classes in order to implement the custom WCF Data Service provider. The following class diagram (generated via Visual Studio Architecture Modeling tools) can help you get an overview of these custom types and their dependency relationships:
![]() |
The source code for this recipe can be found in the \ch01\FileDataServiceSln\
directory.
2. Create the custom class that represents individual file objects (see the following
FileEntity
class definition).public class FileEntity { public int ID { get; set; } public string FileName { get; set; } public string Extension { get; set; } public DateTime Created { get; set; } public long Length { get; set; } }
3. Create the data context class that represents the data source and entity sets container of the sample service.
The following code snippet shows the
DirectoryFileDataContext
class of the sample service:public class DirectoryFileDataContext { public DirectoryInfo DirInfo { get; set; } public List<FileEntity> Files { get; set; } public DirectoryFileDataContext(): this(Environment.CurrentDirectory) { } public DirectoryFileDataContext(string dirPath) { DirInfo = new DirectoryInfo(dirPath); int i=0; Files = (from fi in DirInfo.GetFiles() select new FileEntity { ID = ++i, FileName = fi.Name, Extension = fi.Extension, Created = fi.CreationTime, Length = fi.Length }).ToList(); } }
4. Create a metadata provider class implementing the
IDataServiceMetadataProvider
interface underSystem.Data.Services.Providers
namespace.The following code snippet shows the overall definition of our metadata provider class in this sample:
public class DirectoryFileDataServiceMetadata: IDataServiceMetadataProvider { private string _containerName = ""; private string _namespace = ""; private Dictionary<string, ResourceSet> _resSets = null; private Dictionary<string, ResourceType> _resTypes = null; ...... #region IDataServiceMetadataProvider Members public string ContainerName { get { return _containerName; } } public string ContainerNamespace { get { return _namespace; } } ...... public IEnumerable<ResourceSet> ResourceSets { get { return _resSets.Values; } } ...... #endregion }
In the constructor of the metadata provider, we need to add code to register the resource types and resource sets mapping to the data entities we want to expose in the WCF Data Service.
public DirectoryFileDataServiceMetadata(DirectoryFileDataContext ctx) { _containerName = "DirectoryFiles"; _namespace = "http://odata.test.org/directoryfiles"; _resSets = new Dictionary<string, ResourceSet>(); _resTypes = new Dictionary<string, ResourceType>(); // Init ResourceType set var fileEntityType = typeof(FileEntity); var fileResourceType = new ResourceType( fileEntityType, ResourceTypeKind.EntityType, null, fileEntityType.Namespace, fileEntityType.Name, false ); AddPropertyToResourceType(fileResourceType, "ID", true); AddPropertyToResourceType(fileResourceType, "FileName", false); AddPropertyToResourceType(fileResourceType, "Extension", false); AddPropertyToResourceType(fileResourceType, "Created", false); AddPropertyToResourceType(fileResourceType, "Length", false); _resTypes.Add(fileResourceType.FullName, fileResourceType); // Init ResourceSet set var fileResourceSet = new ResourceSet ("Files", fileResourceType); _resSets.Add("Files", fileResourceSet); }
5. Create a query provider class, which implements the
IDataServiceQueryProvider
interface underSystem.Data.Services.Providers
namespace.The following code snippet shows the main part of our sample
DirectoryFileDataServiceQueryProvider
class:public class DirectoryFileDataServiceQueryProvider: IDataServiceQueryProvider { private DirectoryFileDataContext _ctx = null; private DirectoryFileDataServiceMetadata _metadata = null; public DirectoryFileDataServiceQueryProvider (DirectoryFileDataContext ctx, DirectoryFileDataServiceMetadata metadata) { _ctx = ctx; _metadata = metadata; } #region IDataServiceQueryProvider Members ...... public IQueryable GetQueryRootForResourceSet (ResourceSet resourceSet) { // Our service provider only provides Files entity set return _ctx.Files.AsQueryable(); } public ResourceType GetResourceType(object target) { return this._metadata.Types.Single (rt => rt.InstanceType == target.GetType()); } .... #endregion }
In the previous code snippet, the
GetQueryRootForResourceSet
method is the one in which we return the entity set data based on the requested entity set type parameter.6. Create the main service provider class, which derives from the
DataService<T>
base class (underSystem.Data.Services
namespace) and implements theIServiceProvider
interface.The following is the definition of the main provider class (see the following
DirectoryFileDataService
class) in this sample. It takes a generic parameter which is derived from the data context class we defined earlier.public abstract class DirectoryFileDataService<T> : DataService<T>, IServiceProvider where T : DirectoryFileDataContext { private DirectoryFileDataServiceMetadata _metaProvider = null; private DirectoryFileDataServiceQueryProvider _queryProvider = null; ...... #region IServiceProvider Members public object GetService(Type serviceType) { if (serviceType == typeof(IDataServiceMetadataProvider)) { if (_metaProvider == null) { InitServiceProviders(); } return _metaProvider; } else if (serviceType == typeof(IDataServiceQueryProvider)) { if (_queryProvider == null) { InitServiceProviders(); } return _queryProvider; } else { return null; } } #endregion }
To simplify the code logic, we will define a helper function to encapsulate the initialization code (see the following
InitServiceProviders
function).private void InitServiceProviders() { var dsObj = this.CreateDataSource(); // Create metadata provider _metaProvider = new DirectoryFileDataServiceMetadata(dsObj); // Set the resource types and resource sets as readonly foreach (var type in _metaProvider.Types) { type.SetReadOnly(); } foreach (var set in _metaProvider.ResourceSets) { set.SetReadOnly(); } // Create query provider _queryProvider = new DirectoryFileDataServiceQueryProvider (dsObj, _metaProvider); _queryProvider.CurrentDataSource = dsObj; }
7. Create a new WCF Data Service based on the main service provider and the data context classes created in the previous steps.
The WCF Data Service class is derived from the
DirectoryFileDataService
class, which takes theDirectoryFileDataContext
class as the generic parameter (see the following code snippet).public class FileDataService : DirectoryFileDataService<DirectoryFileDataContext> { public static void InitializeService (DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.DataServiceBehavior.AcceptProjectionRequests = true; config.SetEntitySetAccessRule ("*", EntitySetRights.AllRead); } protected override DirectoryFileDataContext CreateDataSource() { var dc = new DirectoryFileDataContext (@"C:\Users\Public\Pictures\Sample Pictures"); return dc; }
In addition, we need to override the
CreateDataSource
function of the service class and put the file directory initialization code there. You can specify any directory (avoid using Windows system directories for potential permission issues) on the local machine for testing purpose.8. Launch the sample service in the web browser and query the
Files
entity set exposed in it.The following screenshot shows the default query result against the
Files
entity set:We can also add additional query options to filter the query result based on the public properties defined in the
FileEntity
class (see the following screenshot).
In the previous steps, we created all the custom provider classes from bottom to top according to the class structure diagram shown earlier. Now, let's have a look at how they work together in a top-to-bottom approach.
The DirectoryFileDataService<T>
class is the top most type among all the custom provider classes. This class is derived from the DataService<T>
base class so that it can be directly used by WCF Data Service as service class. The DirectoryFileDataService<T>
class also implements the IServiceProvider
interface because it will be asked to provide certain implementations of various kind of custom service providers. In this case, we have implemented the metadata
provider (IDataServiceMetadataProvider
interface) and the query
provider (IDataServiceMetadataProvider interface), which are used for publishing service metadata and exposing entity sets. In addition, there are other providers used for implementing more advanced features, for example, the IDataServiceUpdateProvider
interface for implementing update functions, the IDataServicePagingProvider
interface for implementing paging functions, and the IDataServiceStreamsProvider
interface for implementing data streaming functions. The following diagram shows the calling pipeline from WCF Data Service class to custom service providers and the backend data source objects:
![]() |
The DirectoryFileDataService
type uses instances of the DirectoryFileDataServiceQueryProvider
and DirectoryFileDataServiceMetadata
types to serve the metadata and query service requests. These two provider instances also use the DirectoryFileDataContext
type instance for retrieving the underlying data entity sets' type information and query root object.
Note
Both the DirectoryFileDataServiceQueryProvider
and DirectoryFileDataServiceMetadata
classes have defined a parameter of the DirectoryFileDataContext
class in their constructors. This is a common pattern when implementing custom service providers. Because most of the providers will need to get type information (for the entity or entity sets they will handle) from the data context object, the class constructor is a good place for them to hold such an object reference.
Finally, we come to the FileEntity
class. You might think it is quite similar to the custom entity types we defined in the Using custom data objects as the data source of WCF Data Service recipe. The important difference is that we do not have to apply any additional attributes (such as the DataServiceKey
and DataServiceEntity
attributes) on the FileEntity
class (compared to those entity types used by a custom CLR objects based data source). In other words, by using a custom WCF Data Service provider, we can make use of existing predefined custom data types (whether they have applied those special attributes under System.Data.Services
namespace or not) as OData service entities.
WCF Data Service providers have opened the door for developers to extend an OData service to their own data sources in a very flexible way. By using the provider-based model, we can control almost every aspect of WCF Data Service customization (such as the querying and updating processes). Whenever you want to customize a certain part of a WCF Data Service, just find the corresponding provider interface and implement it.
For more information about building custom WCF Data Service providers, you can refer to the following MSDN reference:
Custom Data Service Providers available at http://msdn.microsoft.com/en-us/data/gg191846