RESTful error handling

 

I’m starting this new entry for the reason to clarify a basic misunderstanding while dealing with RESTful Services. In the last couple of days I was at least twice confronted with a very strange idea. The talk is about returning detailed error messages to the client while the server experiences some exception-pains. So, this is a valid requirement, why wonder? Yes, except someone will ask returning pretty SOAP-like error messages. And in order to give you a hint about such forum discussions going on, here a link you can follow how to extend the System.ServiceModel.Channels.Message (which represents a SOAP Envelop) in order to return the error to the client. This is in my opinion a nonsense or at least a somewhat mislead approach. The reason RESTful Services have evolved is to bypass SOAP or in other words just to ignore SOAP. If you decided to follow the RESTful path, why for the heaven’s sake you would start creating SOAP Envelops just to return structured error messages to the client?

The resolution is easy and you definitely do not need involve the SOAP stack. You can return HTML or XHTML formatted messages or XML or Json, whatever you like. One of the best solutions in this regard is using the WebProtocolException class which is part of the WCF REST Starter Kit Preview 2. This however also expects the host is derived from WebServiceHost2 which is another useful class within that Kit. Just take a look on the WebProtocolException signature in order to have some idea what this class can do for you.

  1 public WebProtocolException(HttpStatusCode statusCode); 
  2
  3 public WebProtocolException(HttpStatusCode statusCode, 
  4          string message, 
  5          Exception innerException); 
  6
  7 public WebProtocolException(HttpStatusCode statusCode, 
  8          string statusDescription, 
  9          object dataContractDetail, 
10          Exception innerException); 
11
12 public WebProtocolException(HttpStatusCode statusCode, 
13          string statusDescription, 
14          string detail, 
15          Exception innerException); 
16
17 public WebProtocolException(HttpStatusCode statusCode, 
18          string statusDescription, 
19          object dataContractDetail, 
20          Func<WebMessageFormat, XmlObjectSerializer> serializerFactory, 
21          Exception innerException); 
22
23 public WebProtocolException(HttpStatusCode statusCode, 
24          string statusDescription, 
25          XElement detail, 
26          bool isDetailXhtml, 
27          Exception innerException);
 

Code 1

If you are in a desperate need for a good article talking about the WCF REST Starter Kit Preview, switch to Aaron Skonnard’s article A Developer’s Guide to the WCF REST Starter Kit . If you are however in the desperate situation, that your host doesn’t derive from WebServiceHost2, then you can still do things well without thinking about SOAP. So let’s tweak my previous ASP.NET MVC hosted RESTful Service example and add some pretty basic exception handling into that service which will return XML (or Json) formatted structured details about the error’s whereabouts.

Prior to start coding let’s remind you, that returning details about service errors should be revised twice and be careful in regard of security. You should not give too much hints to your attackers about you internal structures. Violation of such principles could be used against your service to start targeted attacks. Therefore diagnostics and healthcare of the service should be the primary concern of the Service. I mean, before any client will start complaining about errors and exceptions of any unnatural artifacts, the well serviced Service’s Administrator should be notified in time about such unfortunate happenings.

ASP.NET MVC will by default return HTML formatted error messages in the well known ASP.NET colors and styles. In order to bypass this built-in feature, we have to serialize the error manually in the very same way, we did that for ordinary payload data. So essentially there are two ways to go:

  1. Return pretty well known HTML
  2. Return XML or Json

In the firs case the code has to throw a WebException like the code below depicts. Here a fragment of the PostServiceActionResult Class’s ExecuteResult method from the previous example. (the complete adapted source code you can download at the end of this article)

try
{
    // Deserialize
    user = serializer.ReadObject(context.HttpContext.Request.InputStream) as User;
    user.LastModified = DateTime.Now;
}
catch (SerializationException exception)
{
    throw new WebException(
    "The sent type cannot be understood",
     exception);
}
 

Code 2

In case of a serialization exception this approach the response’s style is familiar (Picture 1). Of course clients not awaiting HTML could be surprised, although note the exception message in this HTML response is framed within <code><pre> [exception goes here] </pre></code> tags, so at least logging such content is a manageable task. Additionally taking a closer look at the response message, the Exception is once more attached to the message directly following the last </html> tag (Picture 2).

 

Picture 1

Picture 2

In the second case (willing to return anything but HTML), you have to think about the data structure you would like to return. The bad news is, that the Exception class is not serializable, so any effort to send back the complete Exception to the client as XML or Json, will fail. What however is possible, just turn the exception to string like html does anyway. So here the Class which holds those information pieces we want to send back to the client.

public class ErrorDetails
{
    public string Description { get; set; }
    public string Source { get; set; }
    public string Message { get; set; }
    public string Exception { get; set; }
}

Code 3

And here is the helper class (Code 4) for assembling such messages on the server, which in turn could be utilized like Code 5 depicts.

private static void ExceptionHelper(ControllerContext context,
                             SerializationException exception,
                             HttpStatusCode statusCode,
                             string description,
                             string format)
{
    ErrorDetails details = new ErrorDetails();
    details.Description = description;  
    details.Message = exception.Message;
    details.Source = exception.Source;
    details.Exception = exception.ToString();

    if (format.ToLower() == "json")
    {
        DataContractJsonSerializer jsonSerializer =
            new DataContractJsonSerializer(typeof(ErrorDetails));
        jsonSerializer.WriteObject(
            (Stream)context.HttpContext.Response.OutputStream, details);
    }
    else
    {
        DataContractSerializer exceptionSerializer =
            new DataContractSerializer(typeof(ErrorDetails));
        exceptionSerializer.WriteObject(
            (Stream)context.HttpContext.Response.OutputStream, details);
    }

    context.HttpContext.Response.StatusCode = (Int32)statusCode;
}

Code 4

public override void ExecuteResult(ControllerContext context)
{
    DataContractSerializer serializer = new DataContractSerializer(typeof(User));
    User user = null;

    try
    {
        // Deserialize
        user = serializer.ReadObject(context.HttpContext.Request.InputStream) as User;
    }
    catch (SerializationException exception)
    {
        ExceptionHelper(context,
            exception,
            HttpStatusCode.BadRequest,
            "The sent type cannot be understood",
            "xml");
        return;
    }

}

Code 5

Testing the approach has the effect, that the client now will receive structured error-messages like Code 6 below depicts.

HTTP/1.1 400 Bad Request
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 1.0
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Thu, 17 Dec 2009 15:59:53 GMT
Content-Length: 2732

<ErrorDetails xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Description>The sent type cannot be understood</Description>
<Message>There was an error deserializing the object of type SharedUserLib.User. Start element 'FirstName_' does not match end element 'FirstName'. Line 1, position 30.</Message>
<Source>System.Runtime.Serialization</Source>
<StackTrace>System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type SharedUserLib.User. Start element 'FirstName_' does not match end element 'FirstName'. Line 1, position 30. ---&gt; System.Xml.XmlException: Start element 'FirstName_' does not match end element 'FirstName'. Line 1, position 30.
   at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
   at System.Xml.XmlUTF8TextReader.ReadEndElement()
   at System.Xml.XmlUTF8TextReader.Read()
   at System.Xml.XmlBaseReader.Skip()
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.SkipUnknownElement(XmlReaderDelegator xmlReader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.GetMemberIndex(XmlReaderDelegator xmlReader, XmlDictionaryString[] memberNames, XmlDictionaryString[] memberNamespaces, Int32 memberIndex, ExtensionDataObject extensionData)
   at ReadUserFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, DataContract&amp; dataContract)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns)
   at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName)
   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName)
   --- End of inner exception stack trace ---
   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName)
   at RestMvcApp.Models.PutServiceActionResult.ExecuteResult(ControllerContext context) in C:\Users\srepas\Documents\Visual Studio 2008\Projects\JunkTestsOnly\RESTFul\BlogArticlesExample\RestMvcApp\RestMvcApp\Models\PutServiceActionResult.cs:line 32
</StackTrace>
</ErrorDetails>

Code 6

Conclusion: do not try setting up a liaison between REST and SOAP, they are not loving each other couples. Think easily, the solutions are straightforward. Prefer the WCF REST Starter Kit Preview whenever possible. Here goes the complete updated download to the source code:http://cid-8d365142bc4869ab.skydrive.live.com/self.aspx/.Documents/RestMvcAppV1a.zip

Advertisements
This entry was posted in Uncategorized - Common. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s