Sunday, June 12, 2011

Show exception details on the client side for WCF RESTful endpoints

In one of my previous posts I described how to create custom error handler for the RESTful endpoints in WCF service. It uses simple idea: store exception message into Status Description field, so e.g. if with standard handler you got something like this:

HTTP/1.1 500 Internal Server Error

Then in with custom error handler you will get the following:

HTTP/1.1 500 InternalServerError: Customer Id can’t be empty

So when such exception will be handled on the client side you will have its actual reason. It was done using the following code in custom error handler:

   1: var rmp = new HttpResponseMessageProperty();
   2: rmp.StatusCode = statusCode;
   3: rmp.StatusDescription = string.Format("{0} {1}", statusCode, error.Message);
   4: fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);

And it works. But recently I found one problem with this approach (quite predictable I would say): with using HTTPS you may get the following exceptions from the server:

The server committed a protocol violation

Because of some reason for simple HTTP protocol it was not thrown (however for HTTPS it also was not regular). Anyway this is a problem which we should address and found more correct way to handle exceptions on the client side.

First of all I turned off my custom handler and ensure that problem is not reproducible without it. Of course I got only generic “Internal Server Error” again. After it I checked from where this exception is thrown on the client (remember that we are talking about RESTful endpoints – which use WebHttpBehavior. JSON and SOAP endpoints are out of scope in this post. I will describe them in the future posts). It is thrown in the WebFaultClientMessageInspector internal class which implements IClientMessageInspector interface:

   1: public virtual void AfterReceiveReply(ref Message reply, object correlationState)
   2: {
   3:     if (reply != null)
   4:     {
   5:         HttpResponseMessageProperty property = (HttpResponseMessageProperty) reply.Properties[HttpResponseMessageProperty.Name];
   6:         if ((property != null) && (property.StatusCode == HttpStatusCode.InternalServerError))
   7:         {
   8:             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(property.StatusDescription));
   9:         }
  10:     }
  11: }

(Another method of the interface BeforeSendRequest(…) just returns null).

Now it became clear why we always got generic “Internal Server Error” exception for the RESTful endpoints. Also it became clear why we can’t use FaultExceptions<FaultContract> for the RESTful service – it is ignored on client side and CommunicationException is thrown instead.

How to solve this problem? The answer is obvious: create custom implementation IClientMessageInspector and use it on client. Remember that it is called on the client side (on server side there is equivalent IDispatchMessageInspector). But how to send error message to the client? I used custom HTTP header with base64 encoded string. Algorithm is the following:

  1. On the server side register and use custom error handler as shown in the previous article.
  2. Instead of modifying of StatusDescription field we store error message in custom HTTP header (preliminary converted to base64 string)
  3. On the client side we use custom message inspector – check the custom HTTP header and if it is not empty, decode it and rethrow exception with initial error message
  4. As result we will have the exception with the same error message as initially thrown from the server

Now lets check the code. First of all we need to modify our custom error handler on the server:

   1: void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
   2: {
   3:     var statusCode = HttpStatusCode.InternalServerError;
   4:     if (error is WebException)
   5:     {
   6:         statusCode = ((WebException) error).Status;
   7:     }
   9:     string errorMsg = error != null ? error.Message : "";
  11:     fault = Message.CreateMessage(version, null, 
  12:         new ErrorBodyWriter {Code = statusCode, Message = errorMsg});
  14:     var rmp = new HttpResponseMessageProperty();
  16:     rmp.StatusCode = statusCode;
  18:     // the following code causes "protocol violation" exception
  19:     //rmp.StatusDescription = string.Format("{0}: {1}", statusCode, error.Message);
  21:     // convert error msg to base64 and store it in custom header. Client will read it via custom message inspector
  22:     if (!string.IsNullOrEmpty(errorMsg))
  23:     {
  24:         rmp.Headers["CustomError"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(errorMsg));
  25:     }
  26:     fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
  27: }

Now we need to catch it on the client. Create custom client message inspector:

   1: public class PoxClientMessageInspector : IClientMessageInspector
   2: {
   3:     public object BeforeSendRequest(ref Message request, IClientChannel channel)
   4:     {
   5:         return null;
   6:     }
   8:     public void AfterReceiveReply(ref Message reply, object correlationState)
   9:     {
  10:         if (reply != null)
  11:         {
  12:             var property = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
  13:             if (property != null)
  14:             {
  15:                 string error = property.Headers["CustomError"];
  16:                 if (!string.IsNullOrEmpty(error))
  17:                 {
  18:                     error = Encoding.UTF8.GetString(Convert.FromBase64String(error));
  19:                     throw new CommunicationException(error);
  20:                 }
  21:             }
  22:         }
  23:     }
  24: }

The only remaining way is to register it on the client side. In order to do it we need to create custom inheritor of the WebHttpBehavior class and register our own message dispatcher instead of standard one:

   1: public class PoxClientBehavior : WebHttpBehavior
   2: {
   3:     protected override void AddClientErrorInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
   4:     {
   5:         clientRuntime.MessageInspectors.Add(new PoxClientMessageInspector());
   6:     }
   7: }

Now in order to register it via config file of our client application we need to create custom extension element:

   1: public class PoxClientBehaviorExtensionElement : BehaviorExtensionElement
   2: {
   3:     protected override object CreateBehavior()
   4:     {
   5:         return new PoxClientBehavior();
   6:     }
   8:     public override Type BehaviorType
   9:     {
  10:         get { return typeof(PoxClientBehavior); }
  11:     }
  12: }

After these manipulations we can register and use our custom message dispatcher in client’s config file:

   1: <system.serviceModel>
   2:   <extensions>
   3:     <behaviorExtensions>
   4:       <add name="poxClientBehavior"
   5:            type="Client.PoxClientBehaviorExtensionElement, Client, Version=, Culture=neutral, PublicKeyToken=..."/>
   6:     </behaviorExtensions>
   7:   </extensions>
   9:   ...
  11:   <behaviors>
  12:     <endpointBehaviors>
  13:       <!-- plain old XML -->
  14:       <behavior name="poxBehavior">
  15:         <poxClientBehavior/>
  16:       </behavior>
  17:       ...
  18:     </endpointBehaviors>
  19:   </behaviors>
  21:   <client>
  22:     <endpoint address=""
  23:       behaviorConfiguration="poxBehavior" binding="webHttpBinding"
  24:       bindingConfiguration="webBinding" contract="IOurService" />
  25:   </client>
  26: </system.serviceModel>

Now on client we will get server’s error message without problems with protocol violation.

No comments:

Post a Comment