WCF IErrorHandler to return FaultException to SOAP

2019-03-12 11:45发布

I have a set of SOAP webservices that are wrapping exceptions using IErrorHandler, specifically:

public sealed class ErrorHandler : IErrorHandler
{
    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // don't wrap existing fault exceptions
        if ((error is FaultException)) return;

        // our basic service fault
        var businessFault = new BusinessFault { FaultMessage = error.Message, FaultReference = "Internal" };

        // Resource based faultReason
        var faultReason = new FaultReason(Properties.Resources.BusinessFaultReason);
        var faultcode = FaultCodeFactory.CreateVersionAwareSenderFaultCode(InternalFaultCodes.BusinessFailure.ToString(), Service.Namespace);

        var faultException = new FaultException<BusinessFault>(
            businessFault,
            faultReason,
            faultcode);

        // Create message fault
        var messageFault = faultException.CreateMessageFault();

        // Create message using Message Factory method
        fault = Message.CreateMessage(version, messageFault, faultException.Action);
    }
}

I have now added extra endpoints for Json and Pox which work fine, unless an exception occurs. In the case of the Json endpoint the FaultException is returned as XML.

I am aware from other SO posts that in the case of REST I would be better throwing a WebHttpException:

throw new WebFaultException<BusinessFault>(detail, HttpStatusCode.BadRequest);

Or overriding the response message properties in ProvideFault, thus:

var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

var rmp = new HttpResponseMessageProperty
{
    StatusCode = System.Net.HttpStatusCode.BadRequest,
    StatusDescription = "See fault object for more information."
};
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);

However, MSDN has some interesting remarks about WebHttpException namely:

When using a WCF REST endpoint (WebHttpBinding and WebHttpBehavior or WebScriptEnablingBehavior) the HTTP status code on the response is set accordingly. However, WebFaultException can be used with non-REST endpoints and behaves like a regular FaultException.

When using a WCF REST endpoint, the response format of the serialized fault is determined in the same way as a non-fault response. For more information about WCF REST formatting, see WCF REST Formatting.

It would suggest therefore that I need to convert my current ProvideFault method to provide a new WebHttpException (wrapping any existing Exceptions or FaultExceptions) and then SOAP would still work as well.

Would anyone like to take a stab at what that would look like (.Net4.0 btw)? I want one error handler to rule them all!

标签: wcf rest soap
2条回答
Deceive 欺骗
2楼-- · 2019-03-12 12:05

In a REST application I'm working on, I created a new class derived from WebFaultException<T> that attaches some additional data to caught service exceptions. Calling the CreatingMessageFault() method on the instance of the derived class let me return my selected exception data from the ProvideFault() method of the error handler as the SOAP fault, letting WCF determine the correct message format.

I am using webHttpBinding to bind all but some third-party services.

Edit: Added code example

public class ErrorHandler : IErrorHandler, IServiceBehavior
{       
    public virtual void ProvideFault( Exception error, MessageVersion version, ref Message fault )
    {
        // Include next level of detail in message, if any.
        MyFaultException myFaultException =
            ((error is MyFaultException) &&
                ((MyFaultException)error).Detail != null)
            ? new MyFaultException(error.Message + " - " +
                    ((MyFaultException)error).Detail.Message, error)
            : new MyFaultException( error.Message, error );
        MessageFault messageFault = myFaultException.CreateMessageFault();
        fault = Message.CreateMessage( version, messageFault, myFaultException.Action );
    }
}

and

/// <summary>
/// Class used to return exception data from my WCF services.
/// </summary>
/// <remarks>
/// This class is used by a web service to pass exception data back and a
/// data object to the client. This class inherits WebFaultException, which
/// is handled specially by the WCF WebServiceHost2 service class and
/// generates a WebException on the client.
/// </remarks>
public class MyFaultException : WebFaultException<BusinessFault>
{
public class MyFaultException : WebFaultException<BusinessFault>
{
    public MyFaultException(string message)
        : this(HttpStatusCode.BadRequest, message) { }

    public MyFaultException(HttpStatusCode statusCode, string message)
        : base(new BusinessFault(message), statusCode) { }
}

then in your service, you can throw the exception to pass fault data to your client:

        try
        {
            // Successful operation proceeds normally.
        }
        catch (ApplicationException e)
        {
            // Failure generates MyFaultException.
            throw new MyFaultException("Operation failed with " + e.Message);
        }
查看更多
Root(大扎)
3楼-- · 2019-03-12 12:08

I was under the impression that using webHttpBinding was a way to get the "all-in-one" functionality of JSON/POX/SOAP as opposed to using separate bindings for each (i.e. wsHttpBinding, basicHttpBinding etc.). So wouldn't you be able to just throw the WebHttpException and then have that give you all the error details you needed regardless of the technology?

查看更多
登录 后发表回答