In this series of posts we have been using ASP.Net MVC to create a RESTful web service. In my last post I looked at returning the full set of standard HTTP Response codes. At the end of that post, the code was handling errors raised during the processing of an action. However, as I said then, that code would not catch all possible errors that are raised during the processing a request.
In this post I’ll look how our web service can handle those other errors instead of relying on the ASP.Net framework to handle them.
In the last post we used the handlers provided within the MVC framework (e.g. the OnException method available on a controller). However, there are some errors raised within the MVC framework for which there are no built in handlers. For instance, if a request cannot be resolved to a controller then a HttpException, with a code of 404 – Not found, is raised. These errors can be handled in the same way any other ASP.Net error could be handled. Here are three more places we could catch errors:
- Create a “Catch All” route for URIs that don’t match any of the other routes;
- Provide an Application_Error in the Global.asax which will catch all unhandled errors;
- Use the customErrors section in the web.config file to redirect to an error page.
The Catch All route is worth providing but its usefulness is limited. If any of the preceding routes match (however inappropriately) the requested URI, then it will not be triggered. I updated the RegisterRoutes method in Global.asax.cs to:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Get", id = "" } // Parameter defaults
);
routes.MapRoute(
"CatchAll",
"{*url}",
new { controller = "Error", action = "Http404" }
);
}
Since the GuidController is the only controller so far, you might hope that all other requests would be sent to the ErrorController. Unfortunately, only requests that are not in the form of {controller}/{action}/{id} are handled by the ErrorController. These requests:
- http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version02/a
- http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version02/a/b
- http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version02/a/b/c
will trigger the default ASP.Net “Resource not found” error. Only when we get to:
- http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version02/a/b/c/d
will the ErrorController be used to handle the request.
Using customErrors is a non-starter because ASP.Net actually uses a client side redirect to send the client to the error page. Therefore, rather than get a 404 – Not Found, the client will get a 302 – Found (effectively a redirect).
The last place to catch all errors is Application_Error in Global.asax.cs. In a ASP.Net Web Forms application we could use Server.Transfer within Application_Error to have an error page display. However, code along the lines of:
void Application_Error(object sender, EventArgs e)
{
Server.ClearError();
Response.Clear();
Server.Transfer("xyz");
}
throws an HttpException, "Error executing child request for xyz." with an inner HttpException exception, "No http handler was found for request type 'GET'". It looks like Server.Transfer is not supported in the MVC framework! The solution I chose was to manually execute an action on a specially created ErrorController:
void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "ErrorController");
routeData.Values.Add("action", "Http500");
if (httpException != null)
{
if (httpException.GetHttpCode() == 404)
{
routeData.Values["action"] = "Http404";
}
}
Server.ClearError();
Response.Clear();
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
This may not be the most elegant solution, So I’m open to suggestions of a better approach. However, once in place, we have full control of the HTTP Response Codes that the web service returns.
The source code is available for download, RESTfulMVCWebService03RC2.zip (33.83 kb) (Updated for Release Candidate 2). In my next post I want to start looking at some of the other HTTP verbs.