Search code examples
c#asp.net-web-apiowinself-hosting

Web-API call not working when using delegate to wrap exception handling


I have a .NET 4.5.2 application with a self hosted OWIN to support a internal web api. To have a general exception handling, I tried to wrap all calls inside a method "TryOk" with a delegate as parameter. TryOk will then care about exceptions and handle them.

I tried to call the web-api without delegate and it worked. only when using delegate I get an error.

I reduced the code to the max and removed all code balast like async.

[HttpPost, Route("echo")]
public IHttpActionResult MyEchoApi([FromBody]string echo)
{
    // this is working: direct return
    //return Ok("you say " + echo ?? "nothing");

    // this is also working: direct return with exception handling
    //try { return Ok(call()); }
    //catch (Exception ex) { return BadRequest(ex.Message); }

    // this is not working: wrapping in delegate
    return TryOk(() => { return Ok("you say " + echo ?? "nothing"); });
}

private IHttpActionResult TryOk<T>(Func<T> call)
{
    try { return Ok(call()); }
    catch (Exception ex) { return BadRequest(ex.Message); }
}

I get the exception "Error getting value from 'Length' on 'Microsoft.Owin.Host.HttpListener.RequestProcessing.HttpListenerStreamWrapper'." with inner exception "This stream does not support seek operations".

Exception details:

"ExceptionMessage": "Error getting value from 'Length' on 'Microsoft.Owin.Host.HttpListener.RequestProcessing.HttpListenerStreamWrapper'.",
"ExceptionType": "Newtonsoft.Json.JsonSerializationException",
"StackTrace": "   bei Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeDictionary(JsonWriter writer, IDictionary values, JsonDictionaryContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeDictionary(JsonWriter writer, IDictionary values, JsonDictionaryContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
   bei Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
   bei Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   bei System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)
   bei System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)
   bei System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
   --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
   bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   bei System.Web.Http.Owin.HttpMessageHandlerAdapter.<BufferResponseContentAsync>d__27.MoveNext()",
"InnerException": {
    "Message": "An error has occurred.",
    "ExceptionMessage": "This stream does not support seek operations.",
    "ExceptionType": "System.NotSupportedException",
    "StackTrace": "   bei System.Net.HttpResponseStream.get_Length()
    bei GetLength(Object )
    bei Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target)"
}

What am I doing wrong?

Thank you for any answer or idea.

-- jaz


Solution

  • You are creating a response over a response and that's why it cause your that error.

    On this line, you have returned an IHttpActionResult in your callback func:

     return TryOk(() => { return Ok("you say " + echo ?? "nothing"); });
    

    you don't need to call Ok(call()), just call return call().

    private IHttpActionResult TryOk<T>(Func<T> call)
    {
        // call itself returns an Ok http action result, you are returning a reposnse on another response! just change to call();
        try { return Ok(call()); }
        catch (Exception ex) { return BadRequest(ex.Message); }
    }
    

    Edit 1:

    you can even make your TryOk method much more smarter by checking the return value of your callbac func.

    try
    {
        var returnValue = call();
    
        if(returnValue is IHttpActionResult)
            return returnValue;
    
        return Ok(returnValue);
    }
    catch(Exception ex)
    {
        return BadRequest(ex.Message);
    }
    

    Another suggestion I have, I recommend you to use A global Exception Filter.