Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am calling an api endpoint in an MVC 6 WebAPI:

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

In the body I have json serialized test entity.

I have a bug in my entity controller code and the api is returning a 500 response 'Server Error' I know what the bug is an will fix it, however the issue I need some help with is that the API is returning HTML instead of the json serialized exception object - Json is what I expect: it's what the old webapi would return. I have ported the coded from an old test project that I know works.

So why is MVC 6 WebAPI returning html rather than json? Is there some configuration I need to do?

EDIT: I added Accept: application/json to headers as suggested by @danludwig, however this did not resolve the issue, I still got an html error page back.

I looked at my StartUp.cs and found:

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

in the ConfigureApp method. I tested with app.UseDeveloperExceptionPage(); commented out. This prevented the return of the html error page in the api response body, however I am still not getting the json serialised exception object.

like image 391
John Avatar asked Feb 06 '16 20:02

John


People also ask

How do I get ASP Net Web API to return JSON instead of XML?

By removing application/xml you remove the ability for the Web API to return XML if the client requests that specifically. e.g. if you send "Accept: application/xml" you will still receive JSON.

Does Web API return JSON by default?

After developing the WebAPI we need to host the service for client. There are different types of client with different type requirement. Means some client need result in XML format and same time some client need it JSON format. By default Web API returns result in XML format.


2 Answers

The 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.

As far as I know you will need to add yourself some piece of code that will handle errors and return a json.

  • One option is to use an exception filter and add it either globally or on selected controllers, although this approach would only cover exceptions coming from the controller action methods. For example the following filter will return a json object only when the request accept was application/json (Otherwise it would let the exception pass through which for example could be handled by the global error page):

    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());
    });
    
  • Another option is to add your own exception handler middleware using the app.UseExceptionHandler overload that lets you specify the behavior of the alternative pipeline that will process the exception. I have quickly wrote a similar example using an inline middleware, which will return a json object only when the request accept was application/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;
            }
        });
    });
    

Both approaches will let you have other error handlers that maybe provide html pages for non json requests (Another idea would be to either return a json or an html page from your custom error handler).

PS. If using the second approach, you most likely want to put that logic into its own middleware class and use a different approach to generate the json response. In that case take a look at what JsonResultExecutor does

like image 174
Daniel J.G. Avatar answered Oct 13 '22 20:10

Daniel J.G.


I found a cheap hack to get what I want by adding this to the Startup Configure method:

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.
}
like image 22
John Avatar answered Oct 13 '22 20:10

John