Basically interceptors are .NET methods exposed by the
service class enabling developers to intercept Http requests and 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 for intercepting HTTP GET requests and allow developers
to handle the reading request. Such methods are decorated with the QueryInterceptor
attribute that simply 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 requests is sent to the service. The code
just 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.The Func
object enables generating anonymous methods on-the-fly, receiving two
arguments: The first one is the real argument, whereas 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 kind of query returns an IQueryable(Of Order),
whereas we need to evaluate the result of an expression tree, and this
is only possible with lambdas. You can easily test this interceptor by
running the service and typing the following Uri in the browser address
bar, replacing the port number:
http://localhost:1443/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 the reason 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 1.
Notice how, other than the error message you provided via the DataServiceException,
there is a lot of information that can be useful to understand what
happened. You thus could implement a log system for redirecting to you,
as a developer, all collected information. Until now we 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,
CRUD 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 (generally a single entity) and the System.Data.Services.UpdateOperations enumeration, which allows understanding what 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 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 CRUD
operation that sent the request.