3

I try to have my WCF services always throw detailed faults, even when not throwing them explicitly. To achieve it, I implemented:

  • an ErrorHandler, whose IErrorHandler.ProvideFault wraps the non-fault error as FaultException

  • a ServiceBehavior extension, attaching this handler AND adding to each operation a fault description of this FaultException, so the client might catch it as such.

I've decorated my service with the error handler attribute (originally I had two distinct implementations of IServiceBehavior, for the ErrorHandler and for the Operation.Faults).

I also made sure the data set into the new FaultDescription is identical to the one I inspected when defining the FaultContract on my contract.

No matter what I try, when using the FaultContract as attribute on my contract, the fault is being properly caught by the client, but when having it attached at runtime through the ApplyDispatchBehavior, only a general FaultException is being caught. Apparently, everything else (error wrapping and throwing) is working, only the addition of a FaultContract to each action at runtime fails.

Please help...

here's the code:

ErrorHandling.cs

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using Shared.Contracts.Faults;

namespace Server.WcfExtensions
{
    public class MyErrorHandler : IErrorHandler
    {
        #region IErrorHandler Members

        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)   
        {
            if (error is FaultException) return;
            if (!error.GetType().IsSerializable) return;

            FaultException<GeneralServerFault> faultExc = new    FaultException<GeneralServerFault>(new GeneralServerFault(error), new FaultReason("Server Level Error"));
            MessageFault messageFault = faultExc.CreateMessageFault();
            fault = Message.CreateMessage(version, messageFault, faultExc.Action);
        }

        #endregion
    }

    class ErrorHandler : Attribute, IServiceBehavior
    {
        Type M_ErrorHandlerType;

        public Type ErrorHandlerType
        {
            get { return M_ErrorHandlerType; }
            set { M_ErrorHandlerType = value; }
        }

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler;
            try
            {
                errorHandler = (IErrorHandler)Activator.CreateInstance(ErrorHandlerType);
            }
            catch (MissingMethodException e)
            {
                throw new ArgumentException("Must have a public empty constructor.", e);
            }
            catch (InvalidCastException e)
            {
                throw new ArgumentException("Must implement IErrorHandler.", e);
            }

            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }

            foreach (ServiceEndpoint ep in serviceDescription.Endpoints)
            {
                foreach (OperationDescription opDesc in ep.Contract.Operations)
                {
                    Type t = typeof(GeneralServerFault);
                    string name = t.Name;

                    FaultDescription faultDescription = new FaultDescription(ep.Contract.Namespace + "/" + ep.Contract.Name + "/" + opDesc.Name + name + "Fault");
                    faultDescription.Name = name + "Fault";
                    faultDescription.Namespace = ep.Contract.Namespace;
                    faultDescription.DetailType = t;
                    opDesc.Faults.Add(faultDescription);
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }
}

GeneralServerFault.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace Shared.Contracts.Faults
{
    [DataContract] //[Serializable]
    public class GeneralServerFault
    {
        [DataMember]
        public SerializableException Wrapped
        {
            get;
            private set;
        }

        public GeneralServerFault()
            : base()
        {
            Wrapped = new SerializableException();
        }

        public GeneralServerFault(Exception toWrap)
            : base()
        {
            Wrapped = new SerializableException(toWrap);
        }
    }

    [Serializable]
    public class SerializableException
    {
        public string Type { get; set; }
        public DateTime TimeStamp { get; set; }
        public string Message { get; set; }
        public string StackTrace { get; set; }

        public SerializableException()
        {
            this.TimeStamp = DateTime.Now;
        }

        public SerializableException(string Message)
            : this()
        {
            this.Message = Message;
        }

        public SerializableException(System.Exception ex)
            : this(ex.Message)
        {    
            if (ex == null) return;
            Type = ex.GetType().ToString();
            this.StackTrace = ex.StackTrace;
        }

        public override string ToString()
        {
            return this.Type + " " + this.Message + this.StackTrace;
        }
    }
}

IContractService.cs

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using Shared.Contracts.Faults;

namespace Shared
{
    internal static class Namespaces
    {
        internal static class Contracts
        {
            public const string ServiceContracts = "http://mycompany/services";
        }
    }

    [ServiceContract(Namespace = Namespaces.Contracts.ServiceContracts, SessionMode = SessionMode.Required)]
    public interface IContactServices
    {
        [OperationContract]
        [FaultContract(typeof(DataNotFoundFault))]
        //[FaultContract(typeof(GeneralServerFault))]
        void DoSomething();
    }
}

ContractService.cs

using System;
using System.Collections.Generic;
using System.ServiceModel;
using Shared;
using Shared.Contracts.Faults;
using Server.WcfExtensions;

namespace Server.Services
{
    [ErrorHandler(ErrorHandlerType = typeof(MyErrorHandler))]
    public class ContactSevices : IContactServices
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
        public void DoSomething()
        {
            throw new InvalidCastException("bla");
        }
    }
}

I omitted the code of client and host

user3058082
  • 203
  • 1
  • 2
  • 7

0 Answers0