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

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

  • Comments 2

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="http://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="http://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


Leave a Comment
  • Please add 7 and 2 and type the answer here:
  • Post
  • I had a problem with the namespaces, the innererror is in the xmlns namespace, so I changed your code as follow:

    [Phani : Aquilax , thanks for catching that, I'll update the Gist in Github]

  • Thanks for the util class!  To get at the internalexception which may even more information (if not the best info):

    Can add a member...

      public Exception InternalException;

    ...an additional constructor...

           public DataServiceException(string Message, string StackTrace, Exception innerException, Exception InternalException)

               : base(Message, InternalException)

           {

               this.InternalException = InternalException;

               _stackTrace = StackTrace;

           }

    ...and modify the switch block...

    case "error":

                   case "innererror":

                   case "internalexception":   // crokusek added

                       DataServiceException innerEx = null;

                       Exception internalEx = null;

                       if (errorElement.Element(xnInnerError) != null)

                       {

                           innerEx = ParseException(errorElement.Element(xnInnerError), throwOnFailure);

                           var e = errorElement.Element(xnInnerError).Element(xnInternalException);

                           if (e != null)

                               internalEx = ParseException(errorElement.Element(xnInnerError).Element(xnInternalException), throwOnFailure);                    

                       }

                       string message = errorElement.Element(xnMessage) != null ?

                                               errorElement.Element(xnMessage).Value.ToString() : String.Empty;

                       string stackTrace = errorElement.Element(xnStackTrace) != null ?

                                               errorElement.Element(xnStackTrace).Value.ToString() : String.Empty;

                       return new DataServiceException(

                           message,

                           stackTrace,

                           innerEx,

                           internalEx

                           );

    still kind of a pain to decode though:

       Exception dsex_;

                   DataServiceExceptionUtil.TryParse(ex, out dsex_);

                   if (dsex_ != null && dsex_ is DataServiceException)

                   {

                       DataServiceException dsex = (DataServiceException) dsex_;

                       // The best info is here extracted from Xml

                       Exception iex = dsex.InternalException;

                       if (iex != null)

                           _sb.AppendLine('\n' + iex.Message + '\n' + iex.StackTrace + "\n\n");

                       if (dsex.InnerException != null)

                           _sb.AppendLine('\n' + dsex.InnerException.Message + '\n' + dsex.InnerException.StackTrace + "\n\n");

                       _sb.AppendLine('\n' + dsex.Message + '\n' + dsex.StackTrace + "\n\n");    

Page 1 of 1 (2 items)