How to use FaultException with details without specifying FaultContractAttribute

352 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
Ding Peng 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
Alex 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)
    );