Handling HTTP 404 Error in ASP.NET Web API

时间:2021-10-23 20:45:44

        Introduction:

Building modern HTTP/RESTful/RPC
services has become very easy with the new ASP.NET Web API framework.
Using ASP.NET Web API framework, you can create HTTP services which can
be accessed from browsers, machines, mobile devices and other
clients. Developing HTTP services is now become more easy for ASP.NET
MVC developer becasue ASP.NET Web API is now included in ASP.NET MVC. In
addition to developing HTTP services, it is also important to return
meaningful response to client if a resource(uri) not found(HTTP 404) for
a reason(for example, mistyped resource uri). It is also important to
make this response centralized so you can configure all of 'HTTP 404 Not
Found' resource at one place. In this article, I will show you how
to handle 'HTTP 404 Not Found' at one place.

        Description:

Let's say that you are developing
a HTTP RESTful application using ASP.NET Web API framework. In this
application you need to handle HTTP 404 errors in a centralized
location. From ASP.NET Web API point of you, you need to handle these
situations,

  • No route matched.
  • Route is matched but no {controller} has been found on route.
  • No type with {controller} name has been found.
  • No matching action method found in the selected controller due to no
    action method start with the request HTTP method verb or no action
    method with IActionHttpMethodProviderRoute implemented attribute found
    or no method with {action} name found or no method with the matching
    {action} name found.

Now, let create a ErrorController with Handle404
action method. This action method will be used in all of the above
cases for sending HTTP 404 response message to the client.

01 public class ErrorController : ApiController
02 {
03     [HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH")]
04     public HttpResponseMessage Handle404()
05     {
06         var responseMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
07         responseMessage.ReasonPhrase = "The requested resource is not found";
08         return responseMessage;
09     }
10 }

You can easily change the above
action method to send some other specific HTTP 404 error response. If a
client of your HTTP service send a request to a resource(uri) and no
route matched with this uri on server then you can route the request to
the above Handle404 method using a custom route. Put this route at the
very bottom of route configuration,

1 routes.MapHttpRoute(
2     name: "Error404",
3     routeTemplate: "{*url}",
4     defaults: new { controller = "Error", action = "Handle404" }
5 );

Now you need handle the case when there is no
{controller} in the matching route or when there is no type with
{controller} name found. You can easily handle this case and route the
request to the above Handle404 method using a custom
IHttpControllerSelector. Here is the definition of a custom
IHttpControllerSelector,

01 public class HttpNotFoundAwareDefaultHttpControllerSelector : DefaultHttpControllerSelector
02 {
03     public HttpNotFoundAwareDefaultHttpControllerSelector(HttpConfiguration configuration)
04         : base(configuration)
05     {
06     }
07     public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
08     {
09         HttpControllerDescriptor decriptor = null;
10         try
11         {
12             decriptor = base.SelectController(request);
13         }
14         catch (HttpResponseException ex)
15         {
16             var code = ex.Response.StatusCode;
17             if (code != HttpStatusCode.NotFound)
18                 throw;
19             var routeValues = request.GetRouteData().Values;
20             routeValues["controller"] = "Error";
21             routeValues["action"] = "Handle404";
22             decriptor = base.SelectController(request);
23         }
24         return decriptor;
25     }
26 }

Next, it is also required to pass the request to
the above Handle404 method if no matching action method found in the
selected controller due to the reason discussed above. This situation
can also be easily handled through a custom IHttpActionSelector. Here is
the source of custom IHttpActionSelector,

01 public class HttpNotFoundAwareControllerActionSelector : ApiControllerActionSelector
02 {
03     public HttpNotFoundAwareControllerActionSelector()
04     {
05     }
06  
07     public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
08     {
09         HttpActionDescriptor decriptor = null;
10         try
11         {
12             decriptor = base.SelectAction(controllerContext);
13         }
14         catch (HttpResponseException ex)
15         {
16             var code = ex.Response.StatusCode;
17             if (code != HttpStatusCode.NotFound && code != HttpStatusCode.MethodNotAllowed)
18                 throw;
19             var routeData = controllerContext.RouteData;
20             routeData.Values["action"] = "Handle404";
21             IHttpController httpController = new ErrorController();
22             controllerContext.Controller = httpController;
23             controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "Error", httpController.GetType());
24             decriptor = base.SelectAction(controllerContext);
25         }
26         return decriptor;
27     }
28 }

Finally, we need to register the custom
IHttpControllerSelector and IHttpActionSelector. Open global.asax.cs
file and add these lines,

1 configuration.Services.Replace(typeof(IHttpControllerSelector), new HttpNotFoundAwareDefaultHttpControllerSelector(configuration));
2 configuration.Services.Replace(typeof(IHttpActionSelector), new HttpNotFoundAwareControllerActionSelector());

        Summary:

In addition to building an
application for HTTP services, it is also important to send
meaningful centralized information in response when something goes
wrong, for example 'HTTP 404 Not Found' error.  In this article, I
showed you how to handle 'HTTP 404 Not Found' error in a centralized
location. Hopefully you will enjoy this article too.