2

Resolved

HttpError only reports the absolute inner exception losing any exception stack

description

If we have an action with the following code:

Exception exception = new Exception("0");
for (int count = 0; count < 10; count++)
{
  exception = new Exception(count.ToString(), exception);
}

throw exception;

then only the inner exception (with message “0”) will get reported to the client:

{"Message":"An error has occurred.","ExceptionMessage":"0","ExceptionType":"System.Exception","StackTrace":null}

The reason is that we use Exception.GetBaseException() which automatically unwraps to the absolute inner exception.

A better model would be to an exception-unwrap implementation that only unwraps certain specific exceptions such as AggregateException and TargetInvocationException.

Henrik

comments

arek wrote Jun 20, 2012 at 9:26 AM

Not sure if it is a bug, just want to let you know that such a message does not allow to deserialize any custom exception on the client side by using base class constructor. If we throw in controller:

throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new MyCustomException("My error message")));

then on client side the attempt to deserialize the response as MyCustomException throws the SerializationException: "Member 'ClassName' was not found" in a serializing constructor:

[Serializable]
public class MyCustomException : Exception
{
public MyCustomException(string message) : base(message) {}
public MyCustomException(string message, Exception inner) : base(message, inner) {}
protected MyCustomException(SerializationInfo info, StreamingContext context)
    : base(info, context) // throws here because ctor of Exception class requires 'ClassName' member (also a few others)
{}
}

youssefm wrote Jun 20, 2012 at 5:31 PM

Json.NET should deserialize the inner exception as a JObject. In Xml, it gets deserialized as a string representing the inner XML. It's up to the user to populate the exception if they really care about deserialization. How are you trying to deserialize the exception exactly?

arek wrote Jun 21, 2012 at 10:01 AM

I meant that JSON message produced from Exception is different when we use Request.CreateErrorResponse and Request.CreateResponse. When we throw as following:

throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new MyCustomException("My error message")));

then JSON is:

{"Message":"An error has occurred.","ExceptionMessage":"My error message","ExceptionType":"Test.MyCustomException","StackTrace":null}

because our exception is wrapped by HttpError, while throwing:

throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, new MyCustomException("My error message")));

produces the following JSON:

{"ClassName":"Test.MyCustomException","Message":"My error message","Data":null,"InnerException":null,"HelpURL":null,"StackTraceString":null,"RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":null,"HResult":-2146233088,"Source":null,"WatsonBuckets":null}

If it is on purpose, it's ok. I was just surprised that CreateErrorResponse provides limited subset of exception members and because of that we are not able to use it to deserialize the exception.

youssefm wrote Jun 21, 2012 at 5:45 PM

Thanks for explaining, arek.

The behavior you're seeing is intended. CreateErrorResponse creates an HttpError that only uses the information off the exception that would be useful to a remote client. You can either deserialize the response as an HttpError. Or you can use CreateResponse if you're really intending on serializing the exception.

Youssef

barumpus wrote Oct 22, 2012 at 11:43 PM

Why does my beautiful outermost exception get thrown away? It tells the client what is wrong in terms that the client understands. My client does not care that a "FileNotFound" exception occured; it cares that the "InvalidPrescriptionState" exception occurred, for example.

For example here is a web service method in my WEBAPI project that attempts to do some tricky math:
    [HttpGet]
    public JObject DoTrickyMath()
    {
        try
        {
            // try to do operation tricky math. it will fail due to unexpected programming bug
            int x = 5; 
            int y = 1 / (5 - x);
        }
        catch (Exception ex)
        {
            // throw exception with more meaning to client; unfortunately the client will not see this message
            throw new InvalidOperationException("unable to do tricky math at this time; some kind of internal exception occured.", ex);
        }
}

In this example I want my client to see "InvalidOperationException" but unfortunately it sees DivideByZeroException. I want to include the InnerException because the exception will be logged by the exception filter registered in global.asax Application_Start(), and the stack trace in the log will be helpful.

This seems so obvious to me: all error displays everywhere always display the outermost exception. I can't fathom when it makes sense to throw that stuff away. (Probably I am just showing my lack of experience or something, but there it is. I said it.)

Thanks for the opportunity to provide feedback!!!

HongmeiG wrote Mar 12 at 11:13 PM

It should be fixed by Youssef's async/await changes.