开发者

System.Exception.Data will not serialise on DataContract?

开发者 https://www.devze.com 2023-03-16 16:00 出处:网络
I have some WCF services using dataContracts and i wanted to I was hoping to pass a Exception with custom Dictionary< string , object > data in the Data property, but when i add any data on this ar

I have some WCF services using dataContracts and i wanted to I was hoping to pass a Exception with custom Dictionary< string , object > data in the Data property, but when i add any data on this array before throwing i get the following error in the ErrorHandler of my custom ServiceBehavior:

Type 'System.Collections.ListDictionaryInternal'

with data contract name 'ArrayOfKeyValueOfanyTypeanyType:http://schemas.microsoft.com/2003/10/Serialization/Arrays' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

Do i invariably need to create a custom exception with a Dictionary property annotated as a DataContract and throw that? the idea of using the ErrorHandler is avoiding to handle exceptions in each service method, do i still need to add further annotations to the methods? what i am missing?

for reference, this is my FaultErrorHandler class:

public class FaultErrorHandler : BehaviorExtensionElement, IErrorHandler, IServiceBehavior
    {
        public bool HandleError(Exception error)
        {
            if (!Logger.IsLoggingEnabled()) return true;
            var logEntry = new LogEntry
            {
                EventId = 100,
                Severity = TraceEventType.Error,
                Priority = 1,
                Title = "WCF Failure",
                Message = string.Format("Error occurred: {0}", error)
            };
            logEntry.Categories.Add("MiddleTier");

            Logger.Write(logEntry);
            return true;
        }

        public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
        {
            var faultException = new FaultException<Exception>( error, new FaultReason(string.Format("System error occurred, exception: {0}", error)));
            var faultMessage = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, faultMessage, Schema.WebServiceStandard);
        }

        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, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
            {
                chanDisp.ErrorHandlers.Add(this);
            };
        }

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

        publ开发者_如何学Pythonic override Type BehaviorType
        {
            get { return typeof(FaultErrorHandler); }
        }

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

my typical service interface looks like:

[ServiceContract(Name = "Service", Namespace = Schema.WebServiceStandard, SessionMode = SessionMode.Allowed)]
    public interface IService 
    {

        [OperationContract(Name = "GetSomething")]
        [FaultContract(typeof(ValidationFault))]
        LookupResult GetSomething();
    }


System.Exception implements ISerializable, which is handled by the serializer the same way as a Dictionary is - it can be [de]serialized, but you need to tell the serializer which types are going to be [de]serialized. In the case of exception, you can't change the class declaration, so if you want to make this scenario work, you'll need to add known types in your service contract (using [ServiceKnownType]) for both the class which will be used for the Data property (which uses the internal type System.Collections.ListDictionaryInternal) and for any types that you'll add to the data dictionary. The code below shows how this can be done (although I'd really advice against that, you should define some DTO types which would handle the information which needs to be returned, to prevent having to deal with the internal implementation details of the Exception class.

public class StackOverflow_6552443
{
    [DataContract]
    [KnownType("GetKnownTypes")]
    public class MyDCWithException
    {
        [DataMember]
        public Exception myException;

        public static MyDCWithException GetInstance()
        {
            MyDCWithException result = new MyDCWithException();
            result.myException = new ArgumentException("Invalid value");
            result.myException.Data["someData"] = new Dictionary<string, object>
            {
                { "One", 1 },
                { "Two", 2 },
                { "Three", 3 },
            };
            return result;
        }

        public static Type[] GetKnownTypes()
        {
            List<Type> result = new List<Type>();
            result.Add(typeof(ArgumentException));
            result.Add(typeof(Dictionary<string, object>));
            result.Add(typeof(IDictionary).Assembly.GetType("System.Collections.ListDictionaryInternal"));
            return result.ToArray();
        }
    }
    [ServiceContract]
    public interface ITest
    {
        [OperationContract]
        MyDCWithException GetDCWithException();
    }
    public class Service : ITest
    {
        public MyDCWithException GetDCWithException()
        {
            return MyDCWithException.GetInstance();
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
        host.Open();
        Console.WriteLine("Host opened");

        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
        ITest proxy = factory.CreateChannel();

        Console.WriteLine(proxy.GetDCWithException());

        ((IClientChannel)proxy).Close();
        factory.Close();

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}


You will also have to add a [KnownType] attribute for any non-system type that you might add to your Dictionary<string , object>. So for example if you add a MyType to the dictionary, then you will need to add [KnownType(typeof(MyType))].

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号