MVC 6 WebAPI 返回 html 错误页面而不是 json 版本的异常 object

MVC 6 WebAPI returning html error page instead of json version of exception object

我正在 MVC 6 Web 中调用 api 端点API:

POST http://localhost:57287/mytestapi/testentity/ HTTP/1.1
Accept: application/json
X-APIKey: 00000000-0000-0000-0000-000000000000
Content-Type: application/json; charset=utf-8
Host: localhost:57287
Content-Length: 1837
Expect: 100-continue
Connection: Keep-Alive

在 body 我有 json 序列化测试实体。

我的实体控制器代码中有一个错误,api 是 return 一个 500 响应 'Server Error' 我知道这个错误是什么会修复它,但是问题我需要一些帮助 API 是 returning HTML 而不是 json 序列化异常 object - Json 是我所期望的: 这就是旧网站 api 会 return 的情况。我从一个我知道有效的旧测试项目移植了代码。

那么为什么 MVC 6 WebAPI returning html 而不是 json?我需要做一些配置吗?

编辑: 我按照@danludwig 的建议将 Accept: application/json 添加到 headers,但这并没有解决问题,我仍然收到 html 错误页面。

我查看了我的 StartUp.cs 并发现:

if (env.IsDevelopment())
{
    //app.UseBrowserLink();
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

在 ConfigureApp 方法中。我测试了 app.UseDeveloperExceptionPage();注释掉了。这阻止了 api 响应 body 中 html 错误页面的 return,但是我仍然没有得到 json 序列化异常 object .

ExceptionHandlerMiddleware configured when using UseExceptionHandler("Home/Error") does not include any support for JSON. It will just return the error html page. The same can be said when using UseDeveloperExceptionPage.

据我所知,您需要自己添加一些代码来处理错误和 return json.

  • 一种选择是使用异常过滤器并将其添加到全局或选定的控制器上,尽管这种方法只能涵盖来自控制器操作方法的异常。例如,仅当请求接受为 application/json 时,以下过滤器才会 return 一个 json 对象(否则它会让异常通过,例如可以由全局错误页面处理) :

    public class CustomJSONExceptionFilter : ExceptionFilterAttribute
    {
    
        public override void OnException(ExceptionContext context)
        {
            if (context.HttpContext.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
            {
                var jsonResult = new JsonResult(new { error = context.Exception.Message });
                jsonResult.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
                context.Result = jsonResult;
            }
        }
    }
    
    services.AddMvc(opts => 
    {
        //Here it is being added globally. 
        //Could be used as attribute on selected controllers instead
        opts.Filters.Add(new CustomJSONExceptionFilter());
    });
    
  • 另一种选择是使用 app.UseExceptionHandler 重载添加您自己的异常处理程序中间件,它允许您指定将处理异常的替代管道的行为。我已经使用内联中间件快速编写了一个类似的示例,只有当请求接受是 application/json 时,它才会 return 一个 json 对象:

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");            
    }
    
    app.UseExceptionHandler(appBuilder =>
    {
        appBuilder.Use(async (context, next) =>
        {
            var excHandler = context.Features.Get<IExceptionHandlerFeature>();                    
            if (context.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
            {
                var jsonString = string.Format("{{\"error\":\"{0}\"}}", excHandler.Error.Message);
                context.Response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
                await context.Response.WriteAsync(jsonString, Encoding.UTF8);
            }
            else
            {                        
                //I haven't figured out a better way of signally ExceptionHandlerMiddleware that we can't handle the exception
                //But this will do the trick of letting the other error handlers to intervene
                //as the ExceptionHandlerMiddleware class will swallow this exception and rethrow the original one
                throw excHandler.Error;
            }
        });
    });
    

这两种方法都会让您拥有其他错误处理程序,这些错误处理程序可能会为非 json 请求提供 html 页面(另一个想法是 return a json 或来自自定义错误处理程序的 html 页面。

PS。如果使用第二种方法,您很可能希望将该逻辑放入它自己的中间件 class 并使用不同的方法来生成 json 响应。在那种情况下,看看 JsonResultExecutor 做了什么

我找到了一个便宜的 hack 来获得我想要的东西,方法是将其添加到 Startup Configure 方法中:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // Simple error page to avoid a repo dependency.
        app.Use(async (context, next) =>
        {
            try
            {
                await next();
            }
            catch (Exception ex)
            {
                if (context.Response.HasStarted)
                {
                    throw;
                }
                context.Response.StatusCode = 500;
                context.Response.ContentType = "application/json";
                var json = JToken.FromObject(ex);
                await context.Response.WriteAsync(json.ToString());
            }
        });
 //Rest of configure method omitted for brevity.
}