Friday, February 24, 2012

Custom Errors in WCF REST Services - Part III

In Part I, I discussed how to handle errors from your WCF services so that you can give your API users a consistent error result.

In Part II, I discussed how to handle a few of those errors generated by WCF that don't get sent to your IErrorHandler implementation.

Now that we've gotten WCF taken care of, there are even still some situations where an end user can get a bunch of HTML instead of a well formed XML response.

IIS Generated Errors
Now that we've gotten past WCF, there's one more layer that needs to be address. IIS has configuration settings that allow you to control the response that's given to a user for certain HTTP status codes. This might work for you, but if you need finer or programtic control, then you'll need to handle these errors yourself. A good example is the 500 Internal Server Error message. Let's say something in ASP.NET is misconfigured. In this scenario, IIS would give the end user an HTML 500 error. The approach I'm going to show for getting around this is for IIS 7.0. The idea is to write a custom IHttpHandler and tell IIS to route any IIS error to that handler. The handler can then return your custom error data contract to the caller. When IIS routes the request to your handler, it passes the error status code as a query string parameter. So all your handler has to do is parse this out and return the appropriate response.
public class ErrorHttpHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.QueryString.Count == 0)
                return;

            string strStatusCode = context.Request.QueryString[0].Split(';').FirstOrDefault() ?? "500";
            int statusCode = 500;
            int.TryParse(strStatusCode, out statusCode);

            string message = "Unhandled server error.";

            switch (statusCode)
            {
                case 400:
                    message = "Bad request.";
                    break;

                case 404:
                    message = "Item not found.";
                    break;
            }

            context.Response.StatusCode = statusCode;
            context.Response.Write(string.Format("<Error><Message>{0}</Message></Error>", message));
        }
    }

The above implementation is only to get you started. The last step is to configure IIS to forward errors to your handler.

<system.webServer>
    <httpErrors errorMode="Custom">
      <clear/> 
      <error statusCode="404" path="/application/ErrorHandler" responseMode="ExecuteURL"/>
      <error statusCode="400" path="/application/ErrorHandler" responseMode="ExecuteURL"/>
    </httpErrors>
    <handlers>
      <add name="ErrorHandler" path="ErrorHandler" verb="*" type="Your Application.ErrorHttpHandler, FrameworkAssembly"/>
    </handlers>
  </system.webServer>

The worst part of this approach is that you have to explicitly define which HTTP response codes you want to handle in your handler. It is possible to have a catch all, but doing so requires you to override the system configuration for IIS and I didn't have much success getting that to work on my machine.

No comments:

Post a Comment