How to use FaultException with details without specifying FaultContractAttribute

340 Views Asked by At

I'm well aware of how to use FaultException with details. I know I can declare details contract, then I need to decorate the method which is expected to throw this kind of exceptions with [FaultContract(type(DetailsContractType))] and then I do throw FaultException in that method. All that is understood and worked. What I need is to be able to throw FaultException from all methods of all contracts in my WCF host. Adding [FaultContract(type(DetailsContractType))] to each method of each operation contract seems to much to me. Is there another way to allow this kind of exceptions without decorating methods with that attribute? If I just remove that attribute everything stops working and the exception becomes just FaultException on the client side. I was thinking about DataContractResolver but it looks like it is not involved in DetailsContractType resolution. Any ideas, hints, solutions?

2

There are 2 best solutions below

0
On

You can implement the IErrorHandler interface to uniformly handle errors in WCF,here is a Demo:

  [ServiceContract]
    public interface IDemo {
        [OperationContract]
        void DeleteData(int dataId);
    }

class DemoService : IDemo
{
    public void DeleteData(int dataId)
    {
        if (dataId<0) {
            throw new ArgumentException("error");
        }
    }
}

The above code is the interface and implementation class of WCF service.

 class MyCustErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            return true;
        }
        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            FaultException faultException = new FaultException(error.Message);
            MessageFault messageFault = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version,messageFault,"my-test-error");
        }
    }

The above code is the implementation class of the IErrorHandler interface.

class MyEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            return;
        }

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

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            MyCustErrorHandler handler = new MyCustErrorHandler();
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(handler);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            return;
        }
    }

We add a custom error handling class by extending the Behavior method of the endpoint.

  ServiceEndpoint ep = selfHost.AddServiceEndpoint(typeof(IDemo), new BasicHttpBinding(), "CalculatorService");
  MyEndpointBehavior myEndpointBehavior = new MyEndpointBehavior();
  ep.EndpointBehaviors.Add(myEndpointBehavior);

The client executes the following code will print "error" in the console:

        try { 
            demoClient.DeleteData(-3);
            }
        catch (FaultException fault) {
            string err = fault.Reason.GetMatchingTranslation().Text;
            Console.WriteLine(err);
        }    

For more information about IErrorhandler,Please refer to the following link:

https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.ierrorhandler?view=netframework-4.8

UPDATE

If you don’t want to use IErrorhandler, you can also use FaultReason:

public string SayHello(string name) {
            if (name.Length<2) {
                FaultReasonText faultReasonText = new FaultReasonText("name length cannot be less than 2");
                FaultReason reason = new FaultReason(faultReasonText);
                throw new FaultException(reason);
            }
            return "hello";
        }

The client needs to catch exceptions when calling:

        try {
                string res = channnel.Sayhello("B");

            }
            catch (FaultException fex) {

                if (fex.Reason != null) {
                    FaultReason reason = fex.Reason;
                    //Get error information
                    FaultReasonText txt = reason.GetMatchingTranslation();
                    Console.WriteLine(txt.Text);
                
                }
            }
0
On

Using IErrorHandler does not relieve you from decorating contract operations with the FaultContractAttribute what I'm trying to avoid. It is even stated in the example you referred, there is a comment there

// This behavior requires that the contract have a SOAP fault with a detail type of 
GreetingFault.

and

    throw new InvalidOperationException(String.Format(
      "EnforceGreetingFaultBehavior requires a "
      + "FaultContractAttribute(typeof(GreetingFault)) in each operation contract.  "
      + "The \"{0}\" operation contains no FaultContractAttribute.",
      opDesc.Name)
    );