OData Programming Cookbook for .NET Developers

2.5 (2 reviews total)
By Steven Cheng
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Building OData Services

About this book

Odata (Open Data Protocol) is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today. OData enables data access among a variety of applications, services, and stores by adopting existing Web technologies such as HTTP, XML, and JSON. This book deals with common OData programming cases over the Microsoft .NET Framework platform and eases the learning curve for a .NET developer to start incorporating OData in data service development.

This book provides a collection of recipes that help .NET developers to get familiar with OData programming in a quick and efficient manner. The recipes cover most OData features from the former ADO.NET Data Service to the current WCF Data Service platform. In addition, all the sample cases here are based on real-world scenarios and issues that .NET developers might come across when programming with OData in application development.

This book will be your handy guide with basic to advanced walkthroughs of common OData programming cases for the Microsoft .NET Framework platform. You will learn quick solutions to necessary tasks to integrate the power of OData at both server-side and client-side.

This book will help you master the use of OData with .NET Framework by taking you through hands-on and practical recipes. It starts by talking about the common means for building OData services and consuming OData services in client applications. Then, some more specific topics like hosting, configuration and security are discussed. The book also covers many popular and interesting topics such as integrating OData in web applications, and developing data-driven mobile applications with OData. Moreover, you can also find quite a few recipes discussing real-world OData producers and new features in latest and future versions.

Within "OData Programming Cookbook for .NET Developers", all the recipes are selected based on real-world scenarios that you will commonly come across. Each recipe covers a specific topic, going from the description of the problem, through a conceptual solution, to a solution containing sample code. By following these recipes, you can acquire how to program with OData in a simple, effective, and easy manner.

Publication date:
July 2012
Publisher
Packt
Pages
376
ISBN
9781849685924

 

Chapter 1. Building OData Services

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

Introduction

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.

 

Introduction


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.

 

Building an OData service via WCF Data Service and ADO.NET Entity Framework


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.

Getting ready

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.

How to do it...

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. 1. Launch Visual Studio 2010 IDE.

  2. 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. 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. 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. 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. 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).

How it works...

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).

See also

  • Exploring an OData service through web browser recipe in Chapter 2, Working with OData at Client Side

  • Applying basic access rules on WCF Data Service recipe in Chapter 3, OData Service Hosting and Configuration

 

Building an OData service with WCF Data Service and LINQ to SQL


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.

Getting ready

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.

How to do it...

You can follow the steps given for creating an OData service from LINQ to SQL data entities:

  1. 1. Create a new ASP.NET Empty Web Application in Visual Studio 2010.

  2. 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.

    Note

    Save all items in the project so as to make sure Visual Studio IDE has compiled the generated LINQ to SQL data model types.

  3. 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. 4. Select the .svc service file in Visual Studio and launch it through the View in browser context menu.

How it works...

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.

There's more...

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

See also

  • Building an OData service via WCF Data Service and ADO.NET Entity Framework recipe

  • Using custom data objects as the data source of WCF Data Service recipe

 

Exposing OData endpoints from WCF RIA Service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 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. 3. Create a new WCF RIA Service by using the Domain Service Class item template in Visual Studio (see the following screenshot).

  4. 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. 5. Create a .svc file as the service access endpoint for the WCF RIA Service.

    In the .svc file, we need to specify the ServiceHostFactory and Service 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 the ServiceHostFactory type in the Factory attribute.

    Note

    If you use the WCF service item template to create a new .svc file, Visual Studio will generate the ServiceContract and Service 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. 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).

How it works...

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.

See also

  • Building an OData service via WCF Data Service and ADO.NET Entity Framework recipe

 

Adding custom operations on OData service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 2. Create an ADO.NET Entity Framework based WCF Data Service through the Northwind sample database.

  3. 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 the Category 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.

    Note

    Don't forget the WebGetAttribute attribute on the declaration of each operation.

  4. 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. 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:

How it works...

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.

See also

  • Building an OData service via WCF Data Service and ADO.NET Entity Framework recipe

 

Exposing database stored procedures in WCF Data Service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 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. 3. Add Function Import for the stored procedures in the Data Model class.

  4. 4. Open the EF designer by double-clicking on the generated data model (.edmx file in Visual Studio Solution Explorer).

  5. 5. Right-click on the designer surface and fire the Function Import... context menu (see the following screenshot).

  6. 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. 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. 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:

How it works...

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).

See also

  • Building an OData service via WCF Data Service and ADO.NET Entity Framework recipe

  • Adding custom operations on OData service recipe

 

Using custom data objects as the data source of WCF Data Service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 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. 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 the BookInfo and BookCategoryx 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. 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 the BookServiceContext 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. 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:

How it works...

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).

There's more...

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

See also

  • Adding custom operations on OData service recipe

  • Building an OData service with WCF Data Service and LINQ to SQL recipe

 

Using Interceptors to customize a WCF Data Service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 2. Create a WCF Data Service with ADO.NET Entity Framework data model (using the Northwind database).

    The service will only expose the Categories and Products entity sets from the data source (see the following screenshot).

  3. 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 have UnitsInStock > 0. The second one is a ChangeInterceptor against the Categories entity set. By using it, no delete operation is allowed on the Categories 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. 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 the UnitsInStock field greater than zero. Also, if we explicitly use query filter to look for Product entities that have UnitsInStock equal to zero, we will get empty results (see the following screenshot).

How it works...

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.

There's more...

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

See also

  • Building an OData service via WCF Data Service and ADO.NET Entity Framework recipe

 

Accessing ASP.NET context data in WCF Data Service


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 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 and SessionItemEntity 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. 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. 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 and ClientInfos 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:

How it works...

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});
}
}
}

See also

  • Using custom data objects as the data source of WCF Data Service recipe

 

Creating a custom WCF Data Service provider


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.

Getting ready

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.

How to do it...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 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. 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. 4. Create a metadata provider class implementing the IDataServiceMetadataProvider interface under System.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. 5. Create a query provider class, which implements the IDataServiceQueryProvider interface under System.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. 6. Create the main service provider class, which derives from the DataService<T> base class (under System.Data.Services namespace) and implements the IServiceProvider 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. 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 the DirectoryFileDataContext 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. 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).

How it works...

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.

There's more...

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

See also

  • Using custom data objects as the data source of WCF Data Service recipe

About the Author

  • Steven Cheng

    Steven Cheng is a senior support engineer at Microsoft Global Technical Support Center where he has been supporting Microsoft software development technologies for more than 5 years. Currently, as a community leader, he is working actively in the MSDN newsgroup and forum communities.

    Steven Cheng's technical specialties cover many popular areas of Microsoft development technologies including .NET framework, ASP.NET, XML WebService, Windows Communication Foundation, SharePoint development, and so on. His blog can be found at http://blogs.msdn.com/stcheng.

    Browse publications by this author

Latest Reviews

(2 reviews total)
Obsolete book, 2010 vintage
Good
Book Title
Access this book, plus 7,500 other titles for FREE
Access now