2

I'm trying to return custom error responses from web.api. Let it be simple string "Oops!" formatted as json. So I created simple delegating handler which replaces error responses like this:

public class ErrorMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
       HttpRequestMessage request, CancellationToken cancellationToken)
    {
       var response = await base.SendAsync(request, cancellationToken);

       if (response.IsSuccessStatusCode)
           return response;

       var formatter = new JsonMediaTypeFormatter();
       var errorResponse = request.CreateResponse(response.StatusCode, "Oops!", formatter);
       return errorResponse;
    }
}

Next I make sure that this is the only one message handler in pipeline:

httpConfig.MessageHandlers.Clear();
httpConfig.MessageHandlers.Add(new ErrorMessageHandler());

// Only default route
httpConfig.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

app.UseWebApi(httpConfig); // OWIN self-hosting

Controller is also simplest one:

public class ValuesController : ApiController
{
   public IHttpActionResult Get(int id)
   {
      if (id == 42)
        return Ok("Answer to the Ultimate Question of Life, the Universe, and Everything");

      return NotFound();
   }
}

And here goes interesting:

  • /api/values/42 gives me 200 response with value string
  • /api/values/13 gives me 404 response with my custom "Oops!" string
  • /api/values/42/missing gives me empty 404 response

The last case is my problem. When I set a breakpoint on the last line of delegating handler I clearly see that errorResponse contains ObjectContent<string> with the correct value. But why this value is cleared away later?

4

1 回答 1

2

原因是HttpMessageHandlerAdapter.InvokeCore中的这段代码(基本上在UseWebApi中间件中):

response = await _messageInvoker.SendAsync(request, cancellationToken);
// ...
if (IsSoftNotFound(request, response)) {
       callNext = true;
}
else { 
    // ...
}

在哪里IsSoftNotFound

private static bool IsSoftNotFound(HttpRequestMessage request, HttpResponseMessage response)
{
    if (response.StatusCode == HttpStatusCode.NotFound)
    {
        bool routingFailure;
        if (request.Properties.TryGetValue<bool>(HttpPropertyKeys.NoRouteMatched, out routingFailure)
            && routingFailure)
        {
            return true;
        }
    }
    return false;
}

所以基本上在“软”404 的情况下,其中“软”表示没有匹配的路由(并且由带有特定键的属性指示request.Properties)- 中间件将调用一些下一个组件。否则 - 只会发送响应。

IsSoftDelete对于您的情况是正确的(因为确实没有匹配的路线)并且下一个组件(没有时间弄清楚那是什么)清除您的响应内容。

要“修复”此问题 - 在请求由先前的处理程序处理后,使用该键从请求中删除属性:

public class ErrorMessageHandler : DelegatingHandler {
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        var response = await base.SendAsync(request, cancellationToken);

        if (response.IsSuccessStatusCode)
            return response;

        // here, can also check if 404
        request.Properties.Remove(HttpPropertyKeys.NoRouteMatched);
        var formatter = new JsonMediaTypeFormatter();
        var errorResponse = request.CreateResponse(response.StatusCode, "Oops!", formatter);
        return errorResponse;
    }
}
于 2017-10-31T16:45:07.120 回答