Chapter 40. Implementing and Consuming WCF Data Services

The growth of networks such as the Internet or local intranets increased the need to implement infrastructures for data exchange between companies or among users. Windows Communication Foundation (WCF) introduced an important unified programming model for information exchange over networks, but implementing a custom logic is not always an easy task. For this, Microsoft created an extraordinary platform named WCF Data Services, which uses the WCF technology and the Open Data Protocol serialization for exposing and consuming data over networks with a unified programming model that can propagate various kinds of data sources based on the .NET Framework and that can offer such infrastructure to the most common client applications types. In this chapter, you start to build REST-enabled, data-oriented applications with WCF Data Services.

What Are Data Services?

WCF Data Services is a framework for exposing data through networks such as the Internet or a local intranet and are based on the Windows Communication Foundation technology. In Visual Studio 2012 and .NET 4.5, you work with version 5.0 of WCF Data Services, which introduces a number of new features. At a higher level, data services are REST-enabled WCF services in that they support the REpresentational State Transfer programming model that enables querying data via HTTP requests. WCF data services can propagate through networks several types of data sources, such as entity data models or in-memory collections that can be consumed by several kinds of client applications, both Windows and web. These include WPF, Windows Forms, Silverlight, and ASP.NET Web Forms or MVC. This data access technology is particularly useful when you need an easy and fast way for implementing data exchange between a server and several clients, which can reach the service just with a web reference, through a unified programming model that can simplify your life—especially when you do not need deep customizations on the business logic. Clients can access data in two ways: via a uniform resource identifier (URI), with HTTP requests, or via a specific LINQ provider known as LINQ to WCF data services. Clients can perform C.R.U.D. operations using LINQ as well. In this chapter you learn to implement WCF data services exposing data from entity data models and consuming them from client applications using both URI and LINQ approaches. As I said before, WCF data services can expose several types of data sources, but the most common scenario (and the most modern) is exposing data from entity data models based on the Entity Framework, as shown in the examples in this chapter.

Querying Data via HTTP Requests

WCF data services can be queried via HTTP requests. This is possible because of the REST approach offered by this particular kind of WCF service, which is a special XML serialization format for data exchange. XML is perfect at this point because it enables you to standardize how data is exchanged in both directions. Querying a Data Service is performed by writing a URI in your web browser addresses bar or in managed code. For example, suppose you have a Data Service exposing data from the Northwind database. When deployed, the service has the following address:

http://localhost:4444/Northwind.svc

HTTP requests sent to a Data Service are represented by the HTTP verbs: GET, (read), POST (update), PUT (insert), and DELETE. The following URI sends a GET request and demonstrates how you can query the Customers collection and get all the Customer objects:

http://localhost:4444/Northwind.svc/Customers

As you will see later when discussing service implementation, this HTTP request can show the full customer list. You can then filter your query results. For example, you can retrieve all orders from the ANATR customer as follows:

http://localhost:4444/Northwind.svc/Customers('ANATR')/Orders

Finally, you could also perform other operations such as ordering via the query string. For instance, the following URI contains a query string that retrieves the same orders as previously mentioned but ordered according to the OrderDate property:

http://localhost:4444/Northwind.svc/Customers('ANATR')/Orders?orderby=OrderDate

For entity data models, a URI can be summarized as follows:

http://website/ServiceName.svc/EntitySetName/NavigationProperty(PrimaryKey)

So far, you have seen how you can use URIs to query a Data Service, but you do not know yet which infrastructure makes this possible. This is something important to know because you need to understand which libraries you will use and because such an infrastructure can open up your mind to new business scenarios. So, it is time to talk about the Open Data Protocol.

Open Data Protocol for WCF Data Services

To accept queries or data operations via HTTP, WCF data services use the Open Data Protocol (OData) by using the REST approach. I suggest you bookmark the OData official website, www.odata.org, because you will find documentation, tutorials, and libraries about data exchange with this protocol. OData is a web protocol for querying and updating data and uses ATOM (that is, XML) and JavaScript Object Notation (JSON, a text-based format used in AJAX applications) serialization modes, and it can be used in an infinite number of scenarios because it is based on a pure web approach rather than on a specific technology. OData exposes data as resources that can be reached via URIs. Clients access and update data by using standard HTTP verbs such as GET, PUT, POST, and DELETE. OData exposes resources as feeds and implements the entity-relationship conventions of the Entity Data Models (EDMs) so that data services can expose entity sets and related data under the form of resources that are related by associations.

A number of libraries make it easier for client applications to access OData resources by using an object-oriented approach rather than sending HTTP requests directly (an updated list is available at http://www.odata.org/libraries). The .NET Framework 4.5 includes a library called Microsoft.Data.OData.dll, which is a client library for accessing OData feeds in managed code and is automatically added to your projects every time you create a WCF Data Service.

Even if in most cases you will want to expose EDMs as OData feeds, OData can expose various data formats. For example, you can expose classic .NET objects or late-bound data.


Geospatial Data in WCF Data Services 5.0

In this new version of the WCF data services, Microsoft has added support for the so-called geospatial primitive types, which are data types for representing geometries and surfaces. This is beyond the scope of this chapter, but in case you are interested in this new addition, you can find further information at: http://www.odata.org/blog/2011/10/14/geospatial-properties


In summary, the greatest benefit of using OData is that data can be shared across a plethora of clients. For instance, you can expose a data service that can be used simultaneously by a Windows Phone application, a WPF application, and an application built with Visual Studio LightSwitch 2012. This should make you immediately understand how many business opportunities can be addressed by using OData.

Implementing WCF Data Services

To implement a WCF Data Service, you first create a web application, add your data source, and finally add a service to your project. The goal of the next example is to expose data within a master-detail relationship from the Northwind database via an EDM. Run Visual Studio 2012 and create a new ASP.NET Empty Web application, naming the new project NorthwindDataService. Figure 40.1 shows the New project window to explain the selection.

Image

Figure 40.1. Creating a new web application for hosting a data service.

When the new project is ready, add a new EDM to the project pointing to the Northwind database, ensuring that Customers, Orders, and Order_Details tables are selected and correctly mapped into the new EDM. If you need a recap on building EDMs, read Chapter 26, “Introducing ADO.NET Entity Framework.”


Support for “Code First”

WCF Data Services 5.0 supports both the ObjectContext class, that you use with Entity Data Models, and the DbContext class that you use with the Code First approach. This is a new feature that enables you to use data models you created in code. For the sake of simplicity, in this chapter an EDM is used.


When ready, in Solution Explorer right-click the project name and select Add new item. In the Add new item dialog box, search for the WCF Data Service template and name the new service NorthwindService.svc, as shown in Figure 40.2.

Image

Figure 40.2. Adding a data service to the project.

After a few seconds, the WCF service is added to the project. If you double-click the NorthwindService.svc file, the code editor lists the autogenerated code expressed in Listing 40.1.

Listing 40.1. Starting Code for a Data Service


Imports System.Data.Services
Imports System.Data.Services.Common
Imports System.Linq
Imports System.ServiceModel.Web

Public Class NorthwindService
    ' TODO: replace [[class name]] with your data class name
    Inherits DataService(Of [[class name]])

    ' This method is called only once to initialize service-wide policies.
    Public Shared Sub InitializeService(ByVal config As DataServiceConfiguration)
        ' TODO: set rules to indicate which entity sets and service operations are
        'visible, updatable, etc.
        ' Examples:

        'config.SetEntitySetAccessRule("MyEntitySet", EntitySetRights.All)
        'config.SetServiceOperationAccessRule("MyServiceOperation",
                                              ServiceOperationRights.AllRead)
        config.DataServiceBehavior.MaxProtocolVersion = _
               DataServiceProtocolVersion.V2
    End Sub
End Class


This is the point where we need to make some considerations. First, WCF data services are implemented by both the Microsoft.Data.Services.dll and Microsoft.Data.Services.Client.dll assemblies, plus the Microsoft.Data.OData.dll assembly that provides managed support for querying the service via the OData protocol. The most important namespaces exposed by such assemblies are System.Data.Services, System.Data.Services.Common, and System.Data.Services.Client. On the server side, they need to work with the System.ServiceModel namespace that provides support for WCF. The entry point of a data service is the System.Data.Services.DataService(Of T) class that is the base class for each service. If you take a look at the code, you see that the NorthwindService class inherits from DataService(Of T). Comments suggest replacing the standard [[class name]] identifier with the appropriate one, which is NorthwindEntities in our case. With that said, the inheritance declaration becomes the following:

Inherits DataService(Of NorthwindEntities)

Notice how the InitializeService method (invoked to start the service) receives a config argument of type DataServiceConfiguration; with this class you can configure the service behavior. For example, you can access authorizations for your data source. The SetEntitySetAccessRule enables establishing access authorizations on entities from the EDM. So, if you want clients to gain full access on the Customers entity, you write the following line:

config.SetEntitySetAccessRule("Customers", EntitySetRights.All)

You need to provide an access rule for each entity. As an alternative, you can use a * character for providing the same access level to all entities. This is not the best approach, but it can be useful for demonstration purposes. Uncomment the line of code for the previously mentioned method and replace the default line with the following one:

'Allows clients performing complete C.R.U.D. operations on all entities
config.SetEntitySetAccessRule("*", EntitySetRights.All)

The access rule is set via one of the EntitySetRights enumeration’s values, which are summarized in Table 40.1.

Table 40.1. EntitySetRights Enumeration’s Values

Image

Just remember that if you want to perform classic insert/update/delete operations, you need to provide All access level. You have just completed the most basic steps for getting a data service up and running. If you now press F5 to start the application, your web browser shows the result of the XML serialization of your data, according to the REST model. This result is shown in Figure 40.3.

Image

Figure 40.3. The data service running shows serialized data in the web browser.


Turn Off RSS Reading View

If you do not get the result shown in Figure 40.3 and instead see an RSS feeds reading view, you need to turn off this view in your browser. If you run Internet Explorer, you can select Tools, Internet Options, Content and then click the Settings button. Then unflag the Turn On Feed Reading View check box. You will need to restart Internet Explorer for the change to take effect.


Notice how the service tag stores the service address. This is important because you use such an address later when instantiating the service. Also notice how the three entity sets (Customers, Orders, and Order_Details) are serialized. Now type the following URI in the browser addresses bar, replacing the port number with the one you see on your machine:

http://localhost:19424/NorthwindService.svc/Customers

This line fetches the full customers list, as shown in Figure 40.4.

Image

Figure 40.4. Fetching the customers list via URI.

You can scroll the page to see how each customer is serialized in the query result. If you look at Figure 40.4, you can easily understand how each customer property is represented. You can also perform some more complex queries. For instance, you might want to retrieve master-details data such as all orders from a specific customer, as in the following URI:

http://localhost:19424/NorthwindService.svc/Customers('ANATR')/Orders?orderby=OrderDate

This URI will retrieve the result shown in Figure 40.5.

Image

Figure 40.5. Retrieving master-details data via URI.

You can perform complex queries via URI, and this is one of the allowed modes for querying data from client applications, so you need to understand how query strings are composed. For this, read the following document from the MSDN Library for a full list of supported operators: http://www.odata.org/documentation/uri-conventions. You do not query your service this way; instead you do it from a client application. This is what the next section begins to show. What has been described up to now is not all about the server side; other interesting features are described later in this chapter, but first you need to know how to reference a data service from the client side.

Deploying WCF Data Services to Internet Information Services

In real-world applications, you will probably host your data services on web servers such as Internet Information Services (IIS). Because they are WCF services, you will deploy them with related techniques described in this page on the MSDN Library: http://msdn.microsoft.com/en-us/library/ms730158(v=vs.110).aspx. For demonstration purposes and for a better focus on services implementation, in this book we deploy data services to the IIS Express development server so that we can take advantage of the Visual Studio environment features.

Consuming WCF Data Services

You consume WCF data services the same way you consume pure WCF services. You need to add a service reference from the client and then instantiate the proxy class. Such a class will be generated for you by Visual Studio 2012 and will expose members for accessing data on the server. As mentioned at the beginning of this chapter, WCF data services can be consumed by various kinds of clients such as Windows (Console, Windows Forms, WPF) and web (Silverlight, ASP.NET Ajax) applications. The next example shows you how to consume data services from a Console client application. Such a project template is useful for focusing on concepts that you can apply to other types of applications.


Note for Silverlight Developers

WCF data services can be consumed from Silverlight applications with the same programming techniques described in this chapter, except that they work asynchronously. If you are interested in this kind of development, I suggest you read this blog post from the Microsoft Visual Studio Data Team Blog: http://blogs.msdn.com/vsdata/archive/2009/10/22/accessing-master-detail-data-through-ado-net-data-service-in-a-silverlight-application-part-1.aspx.


Creating a Client Application

The goal of the next example is to show how you can perform read/insert/update/delete operations against a data service from a client. Follow these steps:

1. Add to the current solution a new Console project and name it NorthwindClient.

2. Right-click the project name and select Add service reference. This adds a reference to the data service similarly to what happens for WCF services. Because in our example the service is available in the current solution, just click Discover. In real applications, you will type the URI of your service. Figure 40.6 shows the Add service reference dialog box.

Image

Figure 40.6. Adding a reference to the ADO.NET Data Service.

3. Replace the default identifier in the Namespace text box with a more appropriate one, such as NorthwindServiceReference; then click OK.


Tip

If the data service also exposes service operations (see next section for details), these will be listed in the right side of the dialog box.


At this point, Visual Studio 2012 generates what in WCF is defined as a proxy class, which is client code. It generates several classes: one class that inherits from System.Data.Services.Client.DataServiceContext and that can be considered as the counterpart for the Entity Framework’s ObjectContext (or for the DbContext class if you used Code First) and a series of counterpart classes for entities in the EDM. This means that, for our example, you have Customer, Order, and Order_Detail classes implemented on the client side. All these classes implement the INotifyPropertyChanged interface so that they can notify the user interface (UI) of changes on the original data source. Instead, the DataServiceContext class also exposes properties of type System.Data.Services.Client.DataServiceQuery(Of T) that are collections of the previously mentioned classes and that represent strongly typed queries against entity sets exposed by a data service. The DataServiceContext class in our example (automatically named NorthwindEntities for consistency with the related context in the service) exposes the Customers, Orders, and Order_Details properties, respectively, of type DataServiceQuery(Of Customer), DataServiceQuery(Of Order), and DataServiceQuery(Of Order_Detail). Although you should never manually edit autogenerated code, if you are curious, you can inspect previously mentioned classes by expanding the NorthwindServiceReference item in Solution Explorer and clicking the Reference.vb file. This is the place where all the client code is implemented. You notice lots of similarities with an entity data model implementation, but do not become confused because WCF data services are a different thing. By the way, such similarities can help you understand how to perform data operations. For example, there are methods for adding objects (AddToCustomers, AddToOrders) and for removing them (DeleteObject). For now, add the following Imports directives that enable you to shorten lines of code:

Imports NorthwindClient.NorthwindServiceReference
Imports System.Data.Services.Client

The next step is instantiating the proxy client class. At module level, add the following declaration:

Private northwind As New  _
        NorthwindEntities(New _
        Uri("http://localhost:1443/NorthwindService.svc"))

Notice how the instance requires you to specify the service URI. This is the same that is specified when adding the service reference. The northwind variable represents the instance of the DataServiceContext class that exposes members for working against entities exposed by the data service and that enables you to perform C.R.U.D. operations. The first operation I am going to explain is insertion. Consider the following function:

Private Function AddNewOrder(ByVal relatedCustomer As Customer) As Order

    Dim newOrder As New Order
    With newOrder
        .Customer = relatedCustomer
        .OrderDate = Date.Today
        .ShipCountry = "Italy"
        .ShipCity = "Milan"
        .ShipName = "First"
    End With

    northwind.AddToOrders(newOrder)
    northwind.SetLink(newOrder, "Customer", relatedCustomer)
    northwind.SaveChanges()
    Return newOrder
End Function

The code first creates an instance of a new order and populates the desired properties. A relationship to the specified customer is also set. This relationship is just set in-memory, but it needs to be explicitly set when sending changes to the actual database. The new order is added to the model via the AddToOrders method, and SetLink explicitly sets the relationship. Such method requires the new object as the first argument, the navigation property in the model as the second argument, and the master object in the master-details relationship. Finally, the code saves the new data to the database by invoking SaveChanges. Later you see how to send to the data source changes in a batch. Performing an update operation is an easy task. You get the instance of the desired object and edit properties. The following snippet demonstrates how to update an existing order:

Private Sub UpdateOrder(ByVal OrderID As Integer)
    'Retrieving the one instance of the specified Order with
    'a lambda.
    Dim ord = northwind.Orders.Where(Function(o) o.OrderID = _
              OrderID).First

    ord.ShipName = "Second"
    ord.ShipCity = "Cremona"
    ord.ShipCountry = "Italy"
End Sub

The code shows how you get the instance of your object and replace properties. If you want to save changes at this point, invoke SaveChanges. We are not doing this now because we will save changes in the batch later.


Extension Methods: The Old and the New

WCF data services do not support First and Single extension methods directly on the data source. This is the reason in the previous code snippet we had to pass through a Where method. WCF Data Services 5.0 add support for the All and Any extension methods so that you can have more granularity when filtering collections. The MSDN Documentation has a specific page about limitations in LINQ when working with data services, available at http://msdn.microsoft.com/en-us/library/ee622463(v=vs.103).


The next step is implementing a deletion method. This is also a simple task, as demonstrated by the following code:

Private Sub DeleteOrder(ByVal OrderID As Integer)

    Dim ord = northwind.Orders.Where(Function(o) o.OrderID = _
              OrderID).First
    northwind.DeleteObject(ord)
End Sub

Also in this case you get the instance of the object you want to remove and then invoke the DeleteObject method. The last step shows how you can save multiple changes to entities in one shot. The following code demonstrates this:

Private Sub SaveAllChanges()
    northwind.SaveChanges(Services.Client.SaveChangesOptions.Batch)
End Sub

SaveChanges receives an argument of type System.Data.Services.Client.SaveChangesOptions, which is an enumeration whose most important value is Batch. This enables you to save all pending changes with a single HTTP request; thus it is efficient with regard to performances. Now we just need to invoke the various methods from within the Sub Main. The following code first creates a new order, updates it, and finally deletes it:

Sub Main()

    Dim cust = northwind.Customers.Where(Function(c) c.CustomerID = _
               "ALFKI").First

    Try
        Dim anOrder = AddNewOrder(cust)

        Console.WriteLine("Added new order: {0}", anOrder.OrderID)

        UpdateOrder(anOrder.OrderID)
        Console.WriteLine("Updated order {0}. ShipCity now is {1},
                          ShipName now is {2}",
                          anOrder.OrderID, anOrder.ShipCity,
                          anOrder.ShipName)

        'Replace the order ID with a valid one
        DeleteOrder(anOrder.OrderID)
        Console.WriteLine("Order deleted")

        SaveAllChanges()

        Console.ReadLine()
        northwind = Nothing

    Catch ex As DataServiceQueryException
        Console.WriteLine("The server returned the following error:")
        Console.WriteLine(ex.Response.Error.Message)
        Console.ReadLine()
    Catch ex As Exception

    End Try
End Sub

The code also is ready for intercepting a DataServiceQueryException—a particular object that provides client information from DataServiceException objects thrown on the server side. If you now run the application, you get messages informing you about the data operations progress, as shown in Figure 40.7.

Image

Figure 40.7. The sample application performs all operations.

Querying Data

One of the most common requirements of any data framework is the capability of performing queries. WCF data services allow two modes on the client side. The first one uses query strings similar to what you can do with URIs. To accomplish this, you invoke the Execute(Of T) method from the DataServiceContext class, where T is the type you want to retrieve a collection of. For example, the following code returns a collection of orders for the specified customer, sorted by order date:

Dim myOrders = Northwind.Execute(Of Order)(New _
               Uri("/Customers('ANATR')/Orders?orderby=OrderDate", _
               UriKind.Relative))

This way is efficient but avoids the strongly typed approach provided by LINQ. Fortunately, .NET Framework also enables you to use a special LINQ provider known as LINQ to Data Services. The following code snippet demonstrates how you can obtain the same result as previously writing a LINQ query:

Dim myOrders = From ord In northwind.Orders
               Where ord.Customer.CustomerID = "ANATR"
               Select ord

This is powerful but not necessarily the best choice. For instance, you might want to prevent indiscriminate data access from clients, or you might want better performance implementing queries on the server side and exposing methods returning query results. This is where service operations take place.

Implementing Service Operations

Service operations are .NET methods that can perform data operations on the server side. With service operations, developers can preventively establish access rules and customize the business logic, such as data validation or access restrictions. They are WCF extensions for data services and perform operations via HTTP requests, meaning that you can execute service operations within a web browser or from a client application. Service operations can return the following types:

IQueryable(Of T) in data-centric scenarios with Entity Framework models or LINQ-to-SQL models

IEnumerable(Of T)

• .NET primitive types because data services can also expose in-memory collections

• No type (Sub methods)

Service operations can be used for both reading and writing data to the service. In a reading situation, service operations are Function methods marked with the WebGet attribute, whereas in writing situations they are decorated with the WebInvoke attribute. The next example explains how to read order details for the specified order and return fetched data to the client. First, add the following method to the NorthwindService class:

<WebGet()> Public Function GetOrderDetails(ByVal OrderID As Integer) _
                  As IQueryable(Of Order_Detail)

    If OrderID > 0 Then
        Dim query = From det In Me.CurrentDataSource.Order_Details
                    Where det.OrderID = OrderID
                    Select det

        Return query
    Else
        Throw New DataServiceException(400,
                                       "OrderID is not valid")
    End If
End Function

The method explanation is quite simple. It performs a validation on the OrderID argument; if valid, it executes a LINQ to Data Services query for getting related order details returning an IQueryable(Of Order_Detail) type. Notice the CurrentDataSource object, which represents the instance of the NorthwindEntities class on the server side. If the OrderID is considered invalid, the method throws a DataServiceException, which is specific for throwing errors from the service. You can specify an HTTP error code and an error message. To make a service operation recognizable and executable, you need to set permissions for it. This is accomplished by invoking the DataServiceConfiguration.SetServiceOperationAccessRule method; therefore, uncomment the following line of code in the InitializeService method:

'config.SetServiceOperationAccessRule _
("MyServiceOperation", ServiceOperationRights.AllRead)

Then you need to replace the operation name as follows:

config.SetServiceOperationAccessRule("GetOrderDetails",
                                          ServiceOperationRights.AllRead)

In our scenario we just need to read data from the service, so the AllRead permission is appropriate. If you now run the service, you can type the following line in the browser address bar to invoke the service operation, being sure to type the appropriate port number of the development server on your machine:

http://localhost:19424/NorthwindService.svc/GetOrderDetails?OrderID=10250

You invoke the operation by writing its name after the service address. Operations’ names and parameters are case-sensitive. You are now ready to call the service operation from the client application. Return to the NorthwindClient project, and add the following method that invokes the service operations for fetching order details:

Private Sub ViewDetails(ByVal OrderID As Integer)
    Console.WriteLine("Showing details for Order ID: " _
                      & OrderID.ToString)

    Dim details = northwind.Execute(Of Order_Detail) _
                  (New Uri("GetOrderDetails?OrderID=" & _
                   OrderID.ToString,
                   UriKind.Relative))

    For Each detail In details
        Console.WriteLine("ID: {0}, Unit price: {1}, Quantity: {2}",
                          detail.OrderID,
                          detail.UnitPrice,
                          detail.Quantity)
    Next
    Console.ReadLine()
End Sub

The code is still quite simple. It invokes the service operation building a query string concatenating the supplied order ID. The Execute(Of Order_Detail) method is invoked because the service operations return a collection of the same type. Such method requires you to specify the URI of the service operation, which in this case is its name followed by the order ID. Before you run the application, you need to update the service reference. This step can be performed later in this case because you do not invoke a managed method, whereas you invoke a service operation via a query string. To update the service reference, in Solution Explorer, just right-click the NorthwindServiceReference item and select Update service reference. Updating the service reference is something you must do each time you perform changes on the service after a reference has been already added in the client application. If you now run the application, you get details for the specified order, as shown in Figure 40.8.

Image

Figure 40.8. Getting order details via a service operation.

If you want to perform insertions, updates, or deletions, you can implement web invokes on the server side. This is accomplished by decorating methods with the WebInvoke attribute. The MSDN documentation provides examples on WebInvoke at this address: http://msdn.microsoft.com/en-us/library/system.servicemodel.web.webinvokeattribute(v=vs.110).aspx.

Implementing Query Interceptors

In the previous section I covered service operations, which act on the server side. But they are not the only server-side feature in data services. Another interesting feature is known as query interceptors. Interceptors are .NET methods exposed by the service class and enable developers to intercept HTTP requests to establish how such requests must be handled, both in reading (query interceptors) and in writing (change interceptors) operations. This section describes both query interceptors and change interceptors.

Understanding Query Interceptors

Query interceptors are public methods intercepting HTTP GET requests and allow developers to handle the reading request. Such methods are decorated with the QueryInterceptor attribute that requires specifying the entity set name. For a better understanding, consider the following interceptor (to be implemented within the NorthwindService class) that returns only orders from the specified culture:

<QueryInterceptor("Orders")> Public Function OnQueryOrders() As  _
                             Expression(Of Func(Of Order, Boolean))
    'Determines the caller's culture
    Dim LocalCulture = WebOperationContext.Current.
                       IncomingRequest.Headers("Accept-Language")

    If LocalCulture = "it-IT" Then
        Return Function(ord) ord.ShipCountry = "Italy"
    Else
        Throw New DataServiceException("You are not authorized")
    End If

End Function

OnQueryOrders will be invoked on the service each time an HTTP GET request is sent to the service. The code returns only orders where the ShipCountry property’s value is Italy, if the client culture (the caller) is it-IT. Differently, the code throws a DataServiceException. The most important thing to notice in the code is the returned type, which is an Expression(Of Func(Of T, Boolean)). This is an expression tree generated starting from the lambda expression actually returned. You may remember from Chapter 20, “Advanced Language Features,” how the Func object enables you to generate anonymous methods on-the-fly, receiving two arguments: The first one is the real argument, and the second one is the returned type. The lambda expression is the equivalent of the following LINQ query:

Dim query = From ord In Me.CurrentDataSource.Orders
            Where ord.ShipCountry = "Italy"
            Select ord

The big difference is that this type of query returns an IQueryable(Of Order), whereas we need to evaluate the result of an expression tree. This is only possible with lambdas. You can easily test this interceptor by running the service and typing the following URI in the browser addresses bar, replacing the port number:

http://localhost:19424/NorthwindService.svc/Orders

This URI automatically fetches only orders targeting Italy, if your local culture is it-IT. If it is not, the Visual Studio debugger shows an exception. By the way, it is important to provide sufficient information about exceptions from the server side because clients need detailed information for understanding what happened. This is a general rule explained here together with a practical example. WCF data services provide a simple way for providing descriptive error messages other than throwing exceptions. This requires the following line of code in the InitializeService method:

config.UseVerboseErrors = True

On the client side, failures from query interceptors are handled by DataServiceQueryException objects. This is why I already implemented such an object in the Try..Catch block in the client application’s main window. According to the previous example, if your culture is different from it-IT, when you run the client application, you should get the error message shown in Figure 40.9.

Image

Figure 40.9. The detailed information about the error.

Notice how, other than the error message you provided via the DataServiceException, there is a lot of information that can be useful to help you understand what happened. You thus could implement a log system for redirecting to you, as a developer, all collected information. So far, we have talked about query interceptors, which intercept GET requests. We now cover change interceptors.

Understanding Change Interceptors

Change interceptors are conceptually similar to query interceptors, but they differ in that they can intercept HTTP requests of type POST, PUT, and DELETE (that is, C.R.U.D. operations via URI). They are public methods returning no type; therefore, they are always Sub decorated with the ChangeInterceptor attribute pointing to the entity set name. Each interceptor receives two arguments: the data source (a single entity) and the System.Data.Services.UpdateOperations enumeration, which enables you to understand which request was sent. Take a look at the following interceptor:

<ChangeInterceptor("Orders")> _
Public Sub OnOrdersChange(ByVal DataSource As Order,
                          ByVal Action As UpdateOperations)

    If Action = UpdateOperations.Add OrElse _
       Action = UpdateOperations.Change Then

        'If data does not satisfy my condition, throws an exception
        If DataSource.OrderDate Is Nothing Then
            Throw New DataServiceException(400,
                      "Order date cannot be null")
        End If

    ElseIf Action = UpdateOperations.Delete Then
        If DataSource.ShippedDate IsNot Nothing Then
            Throw New DataServiceException(500,
            "You are not authorized to delete orders with full info")
        End If

    End If
End Sub

You decide how to handle the request depending on the UpdateOperations current value. Add corresponds to an insert operation, Delete to a delete operation, Change to an update operation, and None means that no operations were requested for the data source. The preceding code performs the same actions on both Add and Change operations and throws an exception if the new or existing order has null value in the OrderDate property. A different check is instead performed about Delete requests; in my example the code prevents you from deleting an order whenever it has value in the ShippedDate property. No other code is required for handling situations in which supplied data are valid because the data services framework automatically persists valid data to the underlying source. Change interceptors come in when a client application invokes the DataServiceContext.SaveChanges method. On the server side, change interceptors are raised just before sending data to the source and collecting information on the C.R.U.D. operation that sent the request.

Understanding Server-Driven Paging

WCF data services offer an interesting feature known as server-driven paging. This feature enables paging data directly on the server and provides developers the ability of specifying how many items a page must return, also offering a URI for browsing the next page. To enable server-driven paging, you invoke the DataServiceConfiguration.SetEntitySetPageSize method that requires specifying the entity set name and the number of items per page. The following code demonstrates this:

config.SetEntitySetPageSize("Customers", 4)

If you now start the service and try to fetch all customers, you get the result shown in Figure 40.10.

Image

Figure 40.10. Server-driven paging demonstration.

For the sake of clarity, all entry items within the browser window are collapsed, but you can see how each of them represents a customer. Therefore, the page shows exactly four items for how it was specified within the service. Another thing that is worth mentioning is the link rel tag that contains the URI for moving to the next four items as in the following:

http://localhost:19424/NorthwindService.svc/Customers?$skiptoken=AROUT

Server-driven paging is intended for use within the service; if you instead require to implement paging on client applications, you can still work with client paging. You accomplish this by specifying $skip and $take clauses within HTTP requests or the corresponding Skip and Take LINQ clauses.

Summary

WCF data services are REST-enabled WCF services supporting HTTP requests such as GET, POST, PUT, and DELETE and constitute a data platform based on the OData protocol for exposing data through networks. In this chapter you got a high-level overview of data services; you first learned about implementing services and how they can be easily created within ASP.NET web applications by adding the WCF Data Service item. Services running within a web browser can then be queried via HTTP requests (URI). Next, you saw how to consume WCF data services from client applications and perform C.R.U.D. operations using the DataServiceContext class that exposes appropriate members for such kinds of operations. After this, you learned how to implement service operations and interceptors for best results on the server side. Finally, you took a tour into server-driven paging for better performances on the server side.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.101.81