ADO.NET Data Services : Efficient Error Handling across Application Tiers

While developing an application that spans multiple tiers , it is important that you be able to flow error information through the tiers without losing
any context or details in the tiers. With ADO.NET Data Services , we have an error contract which guarantees that all errors thrown from the Data Service ,
wrapped inside a DataServiceException , will be represented in a standard way on the wire when we send the error down to the client.

For example , consider the Query Interceptor shown below .

 [QueryInterceptor("Customers")]
public Expression<Func<Customers, bool>> OnQueryCustomers()
{
        string loggedInUser = HttpContext.Current.User.Identity.Name;
        if (UserHasAccessToSet("Customers", loggedInUser))
        {
            //Filter Expression goes here
            return entity => true;
        }
        else //User does not have access to '/Customers' , throw AccessViolationException
        {
            throw new DataServiceException(403,
                "Forbidden",
                String.Format("User '{0}' cannot request data from '{1}' table", loggedInUser, "Customers"),
                "en-US");
        }
}

When the Astoria server runtime throws the above exception , the 403 value specified above  gets turned into the response status code of the request

which caused this exception.

StatusCodeInFiddler 

and the error message is serialized out to be in this format :

 <?xml version="1.0" encoding="utf-8" standalone="yes" ?> 
<error xmlns="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code>Forbidden</code> 
  <message xml:lang="en-US">User 'NoPermissions' cannot request data from 'Customers' table</message> 
</error>

At this point , the service has not lost any information that the service author intended to convey to a service consumer.

Now , lets consider the client library and how it handles this case.

Lets consider a query for the “Customers” set to which the currently logged-in user doesn't have permissions.

 foreach (Customers customerEntity in northwindContext.CreateQuery<Customers>("Customers") ) {
 //Do something with the customerEntity here
}

In this case, running the above code  would result in a DataServiceQueryException being thrown.

 System.Data.Services.Client.DataServiceQueryException: 
  An error occurred while processing this request. --->        
     System.Data.Services.Client.DataServiceClientException: 
      <?xml version="1.0" encoding="utf-8" standalone="yes"?>
        <error xmlns="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
         <code>Forbidden</code>
         <message xml:lang="en-US">User 'NoPermissions' cannot request data from 'Customers' table</message>
      </error>

Note that the DataServiceException thrown by the server is now set to the Message property of the InnerException of the DataServiceQueryException.

We do not de-serialize the DataServiceException into an exception type on the client side.

By default , since the exception is now a string on the client side , you don’t have direct  access to the information contained inside the Exception.

The information is all still there , just not easily accessible anymore. Since the error contract is documented and follows a standard pattern ,

we can easily write a visitor that de-serializes an exception object from the “Message” property  of the Inner Exception.

One such de-serializer for Error contracts is shown at the bottom of this post

It contains two methods :

  1. TryParse : which takes in an exception caused during Querying or updating via the client library and

    returns a DataServiceException that was thrown by the Server

  2. Throw : which takes in an exception caused during Querying or updating via the client library and

    re-throws the DataServiceException that was thrown by the Server