Throwing Exceptions from ADO.NET Data Services

Exception handling can be tricky in a distributed system, and I'd like to use this post to show a couple of interesting behavioral questions.

First, I'm going to start off with the model I've been using so far, and this time I'll ask you to insert the following line in service initialization, just to avoid distracting your attention.

public static void InitializeService(IWebDataServiceConfiguration config)
{
...
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
}

Let's say that something happens while we're processing a request. We'll create a service operation that throws an exception to see what happens.

[WebGet]
public IQueryable<Model.Customers> CustomersThatThrow()
{
throw new Exception("Exception thrown!");
}

If we access this URL, this is what we'll get back:

https://localhost:65314/WebDataService1.svc/CustomersThatThrow

Exception has been thrown by the target of an invocation.
at System.RuntimeMethodHandle._InvokeMethodFast(...)
at System.RuntimeMethodHandle.InvokeMethodFast(...)
at System.Reflection.RuntimeMethodInfo.Invoke(...)
at System.Reflection.RuntimeMethodInfo.Invoke(...)
at System.Reflection.MethodBase.Invoke(...)
at Microsoft.Data.Web.RequestUriProcessor.ProcessRequestUri(...)
at Microsoft.Data.Web.WebDataService`1.ProcessIncomingRequest()
Exception thrown!
at WebApplication3.WebDataService1.CustomersThatThrow() in <project-path>\WebDataService1.svc.cs:line 78

 
There are a couple of things I'd like to draw attention to:
- Right now, we're returning stack traces and exception messages. This can be a security problem, so expect us to lock this down for production systems.
- Note that the browser displays this as-is. The service returned an error code, but also a text/plain response body with our exception information.

The service won't always make the effort to adjust the response headers and then format and serialize the error information. There are certain exceptions that we can't recover safely, and at that point we'll let them bubble out of our system.

Let's say that something really bad happened - we can fake it like this.

[WebGet]
public IQueryable<Model.Customers> CustomersThatThrow()
{
throw new OutOfMemoryException();
}

At this point, if you're running in the debugger, you'll see that the debugger tries to break on an unhandled exception, and then the process terminates. Eeep! However, process termination is always safer than corrupting data, so it's the right thing to do. Most production hosts will restart the process for continued availability.

And for some 'special' processing, we use the Microsoft.Web.Data.Web.WebDataServiceException class. The constructors for this type take a status code as well as a message. This type is recognized by the service, and the status code is set on the response, so instead of a 500 (Internal Server Error), you can reply with, say, a 400 (Bad Request).

[WebGet]
public IQueryable<Model.Customers> CustomersThatThrow()
{
throw new WebDataServiceException(400, "I'm sure the fault is yours, not the server's.");
}

On the next blog post, we'll go just a bit more into how exceptions are handled.