Returning custom HTTP Status codes for WCF SOAP Exceptions

When WCF encounters an unhandled exception, the thrown exception is wrapped up in a FaultException and returned to the client. You can of course throw your own FaultException to have better control over the contents of the error, but one thing you cannot control by default is the HTTP response code. It is always 500.

I had a situation where I needed to return a 503 error under certain circumstances in a WCF SOAP service.

The solution is not as simple as I might like, but it isn’t terrible. There are examples out there for changing the http status code for all exceptions (https://msdn.microsoft.com/es-es/library/ee844556(v=vs.95).aspx), but this was not at all what we want.

The solution uses the same basic framework as the mentioned article however. An Endpoint Behavior Extension registers a Dispatch Message Inspector that watches for faults. In the case of a fault. Inside the BeforeSendReply method, you have access to the reply message. For performance reasons it is best to not unwrap the XML, so we use the SOAP Action header to trigger the HTTP Status code update.

I decided to create a simple custom exception class that sets the SOAP Action to a predefined value

    /// <summary>
    /// Custom Fault Exception that when used with the CustomFaultStatusBehavior Endpoint Behavior
    /// allows returning custom HTTP status codes to the client
    /// </summary>
    public class StatusFaultException : FaultException
    {
        /// <summary>
        /// Create new exception
        /// </summary>
        /// <param name="statusCode">HTTP Status code to be returned to the client</param>
        /// <param name="faultReason">SOAP Fault Reason</param>
        /// <param name="faultCode">SOAP Fault Code</param>
        public StatusFaultException(System.Net.HttpStatusCode statusCode, string faultReason, FaultCode faultCode)
            :base(faultReason, faultCode, "CustomFaultStatus" + ((int)statusCode).ToString())
        {
            //StatusCode is placed in the response Action. Action would be "CustomFaultStatus503" to return a 503 error code
        }
    }

Here is the Custom Behavior that consumes the SOAP Action

    /// <summary>
    /// Endpoint Behavior that allows returning custom HTTP response codes for SOAP Faults 
    /// </summary>
    public class CustomFaultStatusBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        //based on https://msdn.microsoft.com/es-es/library/ee844556(v=vs.95).aspx
        public override Type BehaviorType
        {
            get
            {
                return typeof(CustomFaultStatusBehavior);
            }
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            CustomFaultStatusMessageInspector inspector = new CustomFaultStatusMessageInspector();
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        protected override object CreateBehavior()
        {
            return new CustomFaultStatusBehavior();
        }
    }

    /// <summary>
    /// Message Inspector that updates the HTTP response code for faulted messages with a CustomFaultStatus action
    /// </summary>
    public class CustomFaultStatusMessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            if (!reply.IsFault) return;
            if (!reply.Headers.Action.StartsWith("CustomFaultStatus", StringComparison.Ordinal)) return;
            //get the string value for desired response code
            string statusCodeString = reply.Headers.Action.Substring(17);
            //convert to int
            int statusCodeInt;
            if (!int.TryParse(statusCodeString, out statusCodeInt)) return;

            //cast to HttpStatusCode
            System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.InternalServerError;
            try
            {
                statusCode = (System.Net.HttpStatusCode)statusCodeInt;
            }
            catch (Exception ex)
            {
                return;
            }

            // Here the response code is changed
            reply.Properties[HttpResponseMessageProperty.Name] = new HttpResponseMessageProperty() { StatusCode = statusCode };
        }
    }

The CustomFaultStatusBehavior must be registered in your web.config as a behavior extension, then it must be referenced in an endpoint behavior. Finally this behavior should be applied to the endpoint using the behaviorConfiguration attribute.