HttpResponseException and Result Content

Topics: ASP.NET Web API
Apr 1, 2012 at 8:29 AM
Edited Apr 1, 2012 at 8:44 AM

The behavior of HttpResponseException() seems to have changed in the latest builds. Previously I was able to throw a new HttpResponseException and create the Response to return as part of that response:

 

var errResponse = Request.CreateResponse<ApiMessageError>(HttpStatusCode.InternalServerError,
new ApiMessageError() { message = ex.Message });
throw new HttpResponseException(errResponse);

This worked previously to return the serialized object to the client for the error response.

Now however I get just a 500 error  - regardless of what HttpStatusCode I set - and an empty response on the client. throw new HttpResponseException() also doesn't trigger a registered Exception filter, so it looks like the framework is definitely doing something special to intercept this error.

Is this new behavior what's supposed to happen? If so,  it's kind of pointless to set the status code and object :-)

On a related note: If I use an ExceptionFilter it seems I also don't get a reponse body. Using a filter like this:

public class UnhandledExceptionFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext
                                        context)
    {
        HttpStatusCode status = HttpStatusCode.InternalServerError;

        var exType = context.Exception.GetType();

        if (exType == typeof(UnauthorizedAccessException))
            status = HttpStatusCode.Unauthorized;
        else if (exType == typeof(ArgumentException))
            status = HttpStatusCode.NotFound;

        var apiError = new ApiMessageError() { message = context.Exception.Message };

        // create a new response and attach our ApiError object
        // which now gets returned on ANY exception result
        var errorResponse = context.Request.CreateResponse<ApiMessageError>(status,apiError);
        context.Response = errorResponse;
            
        base.OnException(context);
    }
}
also returns a 500 error *regardless* of what error code I send in the context.Response.

+++ Rick ---

Apr 1, 2012 at 2:03 PM

Hmm, that seems broken. We’ll have a look.

Henrik

From: rstrahl [email removed]
Sent: Sunday, April 01, 2012 1:30 AM
To: Henrik Frystyk Nielsen
Subject: HttpResponseException and Result Content [ASPNETWebStack:350786]

From: rstrahl

The behavior of HttpResponseException() seems to have changed in the latest builds. Previously I was able to throw a new HttpResponseException and create the Response to return as part of that response:

var errResponse = Request.CreateResponse<ApiMessageError>(HttpStatusCode.InternalServerError,
new ApiMessageError() { message = ex.Message });

throw new HttpResponseException(errResponse);

This worked previously to return the serialized object to the client for the error response.

Now however I get just a 500 error - regardless of what HttpStatusCode I set - and an empty response on the client.

Is this new behavior what's supposed to happen? If so, it's kind of pointless to set the status code and object :-)

+++ Rick ---

Developer
Apr 1, 2012 at 3:55 PM
Edited Apr 1, 2012 at 3:55 PM

Hi Rick,

I tried the following scenario and wasn't able to repro the problem you were mentioning..please let me know if this scenario is not something similar to yours:

Throwing ArgumentException at Action method->Exception filter is invoked, where CreateResponse<T> was used to create a body and explicit status code -> Client is receiving a con-neged response with the expected status code and body set in Exception filter.

FYI...

 

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Exception.GetType() == typeof(ArgumentException))
            {
                actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(HttpStatusCode.BadRequest, "custom error message");
            }

            base.OnException(actionExecutedContext);
        }
    }

 

 

NOTE: Here if you notice i am setting a simple 'string' in the response body, but where as in your case you are having "ApiMessageError". I would like to point this difference out because if there was any error during write of this ApiMessageError by the formatter, then our currently behavior in WebHost scenarios is to send back the client a 500 Internal Server Error with no body

In view of the above 'NOTE', can you please try to see if your scenario works when you use 'string' body so that we can narrow down the problem?

Thanks,

Kiran Challa

Apr 1, 2012 at 9:31 PM
Edited Apr 1, 2012 at 9:35 PM

Kiran,

Using:

 
var errorResponse = context.Request.CreateResponse(HttpStatusCode.BadRequest, 
                                        context.Exception.GetBaseException().Message);
context.Response = errorResponse;

in the exception filter works.

Using a custom type however does not:

var errorResponse = context.Request.CreateResponse<ApiMessageError>(status,apiError);

In the former case I get the right response encoded in XML (from browser) and the proper status code.

For the latter I get a 500 error and no response. My filter code fires and the Response gets set properly, but I get an error so it looks like the framework is blowing up after the fact. This worked in the beta and builds after.

From the looks of it both issues might be related. I can HttpResponseException() to work if I create a simple string response - it only seems to fail on the complex type.

 

+++ Rick ---

Developer
Apr 1, 2012 at 10:24 PM
Edited Apr 1, 2012 at 10:28 PM

Rick, thanks for trying it out. This narrows down the problem. 

Can you show us how does your custom type ApiMessageError looks like?

More details about my previous post's 'NOTE' :

Recently we have made some changes(for WebHost scenarios) related to providing a better user experience when formatters throw exception while writing to stream. Previously, we used to abruptly close the connection...but currently we do buffering for ObjectContent and hence when formatter tries to write and if there was any exception, instead of closing the connection abruptly we send back the client a 500 Internal Error with no body. I am guessing this what might be happening in your case.

BTW, may i ask which version of source code are you using currently?

Thanks,

Kiran Challa

Apr 1, 2012 at 11:10 PM
Edited Apr 1, 2012 at 11:11 PM

kiran,

The type is very simple:

 

 

    public class ApiMessageError
    {
        public string message { get; set; }
        public bool isCallbackError { get; set; }
        public List<string> errors { get; set; }

        public ApiMessageError(string errorMessage = null)
        {
            isCallbackError = true;
            errors = new List<string>();
            message = errorMessage;
        }
    }

as mentioned this worked fine in the post beta builds.

+++ Rick ---

Developer
Apr 2, 2012 at 4:13 AM

Rick, I tried using your custom type in my repro sample and as expected the WriteToStreamAsync method of Xml formatter is throwing exception because of which you were seeing the 500 Internal Server Error with no body.(the reason behind this behavior is in my previous post)

Exception Details:

System.AggregateException: One or more errors occurred. ---> System.Runtime.Serialization.InvalidDataContractException: Type 'MvcWebApi.MainTests.Tests.SampleTests.ApiMessageError' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  If the type is a collection, consider marking it with the CollectionDataContractAttribute.  See the Microsoft .NET Framework documentation for other supported types.     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)     at System.Runtime.Serialization.DataContract.GetDataContract(Int32 id, RuntimeTypeHandle typeHandle, SerializationMode mode)     at System.Runtime.Serialization.DataContractSerializer.InternalWriteStartObject(XmlWriterDelegator writer, Object graph)     at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)     at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)     at System.Net.Http.Formatting.XmlMediaTypeFormatter.<>c__DisplayClass7.<WriteToStreamAsync>b__6() in e:\WebstackRuntime\Runtime\src\System.Net.Http.Formatting\Formatting\XmlMediaTypeFormatter.cs:line 343     at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token) in e:\WebstackRuntime\Runtime\src\Common\TaskHelpers.cs:line 140     --- End of inner exception stack trace ---  ---> (Inner Exception #0) System.Runtime.Serialization.InvalidDataContractException: Type 'MvcWebApi.MainTests.Tests.SampleTests.ApiMessageError' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  If the type is a collection, consider marking it with the CollectionDataContractAttribute.  See the Microsoft .NET Framework documentation for other supported types.     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)     at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)     at System.Runtime.Serialization.DataContract.GetDataContract(Int32 id, RuntimeTypeHandle typeHandle, SerializationMode mode)     at System.Runtime.Serialization.DataContractSerializer.InternalWriteStartObject(XmlWriterDelegator writer, Object graph)     at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)     at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)     at System.Net.Http.Formatting.XmlMediaTypeFormatter.<>c__DisplayClass7.<WriteToStreamAsync>b__6() in e:\WebstackRuntime\Runtime\src\System.Net.Http.Formatting\Formatting\XmlMediaTypeFormatter.cs:line 343     at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token) in e:\WebstackRuntime\Runtime\src\Common\TaskHelpers.cs:line 140<---

Apr 2, 2012 at 4:43 AM

Kiran,

Turns out it's because there's no default constructor. 

 

public ApiMessageError(string errorMessage = null)

Even though there's a default value (so the value can be omitted) I guess that doesn't qualify as a parameterless constructor required for serialization to work (XML Serialization at least). 

I changed to break out into two constructors and now everything works as expected.

One thing that would be useful here is that if we are running in debug mode to get some sort of error trace output. Or a config flag that sets this? Really hard to tell what's not working in this scenario. Only reason I noticed is because I have another sample that returns ApiErrorMessage directly (not as part of exception handling) and that failed as well. 

Thanks for you help - you did get me looking in the right place!

+++ Rick ---

 

 

 

Developer
Apr 2, 2012 at 5:27 AM

Cool...sure, we are seeing if we could provide better exception details in view of your experience here...thanks! for bringing it up...