Modifying HTTP Error Codes, Part 2
Let's pick up where we left off last time with the question…
How do I modify the HTTP status code that gets sent back with a fault?
It's clear that we need to plug into the fault generation process somehow, but in past articles we've only seen fault handling for channels rather than services. Is there an equivalent for FaultConverter that we can use with our service? Well, partially. There's an IErrorHandler interface that very slightly overlaps the functionality of FaultConverter, but fortunately that's exactly the part we need. IErrorHandler allows us to replace the message faults that get created by the service. We can modify the status code by attaching a message property to the fault for the HTTP response. Here's what that looks like. I'll change the normal internal server error status code to one that indicates that the service requires payment to make the method call.
class HttpErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
HttpResponseMessageProperty properties = new HttpResponseMessageProperty();
properties.StatusCode = HttpStatusCode.PaymentRequired;
fault.Properties.Add(HttpResponseMessageProperty.Name, properties);
}
}
}
Now, we need some way to attach this error handler to the service. Since this example is using IIS hosting, there are fewer points to hook in as we don't own the service host. I'll create a new server behavior attribute that attaches an IErrorHandler instance to the service. As IErrorHandler interfaces with the service dispatcher, I need to fish out the dispatchers from the service host.
class ErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;
public ErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}
public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
Once we attach the behavior attribute to the service from last time, we've completed the example.
[ServiceContract]
public interface IService
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message Action(Message m);
}
[ErrorBehavior(typeof(HttpErrorHandler))]
public class Service : IService
{
public Message Action(Message m)
{
throw new FaultException("!");
}
}
Let's telnet to the service address again and see what that did to the HTTP headers.
HTTP/1.1 402 Payment Required
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.0
X-Powered-By: ASP.NET
Date: Wed, 10 Jan 2007 04:54:04 GMT
Connection: close
Content-Length: 159
<Fault xmlns="https://schemas.microsoft.com/ws/2005/05/envelope/none"><Code><Value>Sender</Value></Code><Reason><Text xml:lang="en-US">!</Text></Reason></Fault>
Exactly as expected, everything is the same except for the HTTP status code.
Next time: Errors Without Context
Comments
Anonymous
January 24, 2007
Back to errors and faults for a bit with this two part series on modifying the HTTP status code usedAnonymous
February 19, 2007
I haven't forgotten about the goal to put together a table of contents for all of these articles. The