10
votes

I have a fairly simple web API application that currently has one route setup. If the user attempts to access any other route they get a 404 back but the body of the 404 is HTML instead of JSON (which is what their accept header asks for). What is the easiest (least code/config added to my project) way to get IIS to respond to requests of non-existant routes with a JSON error response rather than a webpage?

The same question applies to 403s. If I try to navigate to the root of my application I get a 403 back, once again as a webpage. I would prefer this be a 404, but the bigger question is how do I make all responses from my application be JSON rather than only responses on valid routes? I mention the 403 because I am looking for a broader solution than just setting up a catch-all route, since I don't believe that will help me with the 403 or any other random exception that occurs outside of a controller action.

Preferrably, I would like my application to respect the accept header on the request. However, since my API only supports JSON right now, I am willing to live with (for the time being) it always responding with JSON.

I'm using WebAPI 2.2 and .NET 4.5.2.

Edit

This isn't a formatter issue. The formatters are correctly applied to successful messages, respecting the accept header in the request. This is specifically an issue with unhandled routes and web server errors (like forbidden when trying to access web root).

Edit 2

Steps to reproduce:

  1. Open Visual Studio 2013 Update 4
  2. New Project
  3. Choose .NET Framework 4.5.2
  4. Choose Templates > Visual C# > Web > ASP.NET Web Application
  5. Choose "Empty", leave all folders and core references unchecked.
  6. Right Click on the project > Add > New Scaffold Item > Web API 2 Controller - Empty
  7. Modify DefaultController as shown below.
  8. Modify WebApiConfig.cs as shown below.
  9. Run/Debug

What I expect is that when I navigate to http://localhost:<port>/api/do_stuff I see Success! in either XML or JSON depending on accept headers (which I do) and when I navigate to any other page I should see Failure! (which I don't). Instead when I navigate to any other page I see IIS's 404 response.

DefaultController.cs:

[RoutePrefix("api")]
public class DefaultController : ApiController
{
    [HttpGet]
    [Route("do_stuff")]
    public String DoStuff()
    {
        return "Success!";
    }
}

WebApiConfig.cs:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Filters.Add(new CustomExceptionFilter());
    }

    private class CustomExceptionFilter : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                actionExecutedContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.NotFound)
                {
                    Content = new StringContent(@"""Failure!"""),
                };
            }
        }
    }
}

Edit 3

I have also tried adding a global exception handler and exception logger as shown here: http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling

Neither are called when I try to navigate to an invalid route or navigate to site root in the above example.

2
If the accept header is text/html and not application/json then returning HTML might be the correct thing to do anyhow.Martin Brown
I don't believe that answer applies here because the accept header is currently set to application/json yet I am getting back text/html. This implies that my formatters aren't being respected for missing routes and forbidden requests.Micah Zoltu
Though I will check the steps mentioned by you and revert, but can you make CustomExceptionFilter a standalone public class, what's the point of making it a private class, my understanding is even when you have registered, it will only be working for exception inside WebAPIConfig and that's why it is not firing on any of the exception encounteredMrinal Kamboj
Making the exception filter a non-nested public class did not change the behavior. I made it private for design/style reasons, it doesn't need to be instantiated or accessed outside of WebApiConfig so all making it public would do is pollute the namespace. It implements IExceptionFilter, which is public, and that is what gets passed around.Micah Zoltu
Thanks for the verification, this seems to be the case where Web API code does not seems to be even called, Error seems to be returning from the web server itself as it cannot get a valid resource or there's no permission to access the site root. My understanding is any custom code exception will be caught and if it does, then we have veered away from the main question. In that case exception filters cannot handle such cases, as it would require exception to be thrown in the Web API executionMrinal Kamboj

2 Answers

0
votes

Call the function below from your planned catch-all-route to return a jsonResult Action like below

public JsonResult Handle404()
{
   return Json(new { error404 = "NOT FOUND", Message = "Additional Message" });
}

which will produce

var result =
{
  "error404": "NOT FOUND",
  "Message": "Additional Message"
}; 

alert(result.error404);
allert(result.Message);
0
votes

Call httpget for response 404 notfound use this

public JsonResult DoSomething() {
        try
        {

            var result = getData();
            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            return Json(HttpNotFound());
        }

    }