如何设置 WCF 自定义反序列化错误消息

How set a WCF custom deserialization error message

我正在创建一个 WCf 网络服务,我想知道如果枚举 DataMember 的反序列化失败,是否可以设置自定义错误消息?

我有一个这样的枚举

    [DataContract]
    public enum VehicleBrand
    {
        [EnumMember]
        BMW = 0,
        [EnumMember]
        Honda = 1,
        [EnumMember]
        Tesla = 2
    }

还有我的 DataContract:

    [DataContract]
    public class MyCar
    {
        [DataMember(IsRequired = true, Order = 1)]
        [Required(ErrorMessage = "Car brand is required.")]
        [EnumDataType(typeof(VehicleBrand), ErrorMessage = "Car brand is required.")]
        public VehicleBrand CarBrand { get; set; }
    }

例如,如果有人通过 SoapUI 调用我的网络服务并将“Mercedes”作为 CarBrand 的值,他将收到一些错误消息,指出无法反序列化 CarBrand...是否可以自定义此错误例如说“CarBrand 应该是 BMW、Honda 或 Tesla”的消息?

我正在使用 DevTrends.WCFDataAnnotations.ValidateDataAnnotationsBehavior 来验证 WCF 的 DataContracts。

谢谢

默认情况下,当您提交包含无效数据的请求时会收到反序列化错误。错误 returned 包含类似

的消息
<Message>Invalid enum value 'Banana' cannot be deserialized into type 'WcfLibrary.DataType'. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.</Message>

因为您在数据协定中定义了枚举,所以它也作为简单类型添加到 wsdl 中,并限制了您添加的所有可能条目。

<xs:simpleType name="DataType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="Car"/>
        <xs:enumeration value="Bike"/>
        <xs:enumeration value="Truck"/>
    </xs:restriction>
</xs:simpleType>
<xs:element name="DataType" type="tns:DataType" nillable="true"/>

换句话说,您可以在将传入消息发送到服务器之前根据 wsdl 在源处验证传入消息。

如果您想控制故障消息,我认为您需要更改数据协定,以便接受字符串而不是枚举。然后尝试将字符串与枚举相匹配,当匹配失败时 return 自定义错误消息。

您可以使用 IErrorHandler 来完成:

Client.cs:

  class Program
    {
        [DataContract]
        public enum MyDCServer
        {
            [EnumMember]
            Test = 0
        }
        [ServiceContract]
        public interface ITestServer
        {
            [OperationContract]
           [FaultContract(typeof(string))]
            MyDCServer EchoDC(MyDCServer input);
        }
        static Binding GetBinding()
        {
            BasicHttpBinding result = new BasicHttpBinding();
            return result;
        }
        static void Main(string[] args)
        {
            string baseAddress = "http://localhost:8000/Service";
            ChannelFactory<ITestServer> factory = new ChannelFactory<ITestServer>(GetBinding(), new EndpointAddress(baseAddress));
            ITestServer proxy = factory.CreateChannel();
            MyDCServer uu = proxy.EchoDC(MyDCServer.Test);
            Console.WriteLine(uu);
            Console.ReadKey();
        }
    }

客户端传来的值为Test。

Server.cs:

 public class Post
    {
        [DataContract]
        public enum MyDCServer
        {
            [EnumMember]
            BMW = 0,
            [EnumMember]
            Honda = 1,
            [EnumMember]
            Tesla = 2
        }
        [ServiceContract]
        public interface ITestServer
        {
            [OperationContract]
           [FaultContract(typeof(string), Action = Service.FaultAction)]
            MyDCServer EchoDC(MyDCServer input);
        }
     
        public class Service : ITestServer
        {
            public const string FaultAction = "http://my.fault/serializationError";
            public MyDCServer EchoDC(MyDCServer input)
            {
                Console.WriteLine(input);

                return input;
            }
        }
        public class MyErrorHandler : IErrorHandler
        {
            public bool HandleError(Exception error)
            {
                return error is FaultException && (error.InnerException as SerializationException != null);
            }

            public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
            {
                if (error is FaultException)
                {
                    SerializationException serException = error.InnerException as SerializationException;
                    if (serException != null)
                    {
                        string detail = String.Format("{0}: {1}", serException.GetType().FullName, serException.Message);
                        FaultException<string> faultException = new FaultException<string>(detail, new FaultReason("CarBrand should be BMW, Honda or Tesla"));
                        MessageFault messageFault = faultException.CreateMessageFault();
                        fault = Message.CreateMessage(version, messageFault, Service.FaultAction);
                    }
                }
            }
        }
        public class MyServiceBehavior : IServiceBehavior
        {
            public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
            {
            }
            public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                foreach (ChannelDispatcher disp in serviceHostBase.ChannelDispatchers)
                {
                    disp.ErrorHandlers.Add(new MyErrorHandler());
                }
            }
            public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
                {
                    if (endpoint.Contract.ContractType == typeof(IMetadataExchange)) continue;
                    foreach (OperationDescription operation in endpoint.Contract.Operations)
                    {
                        FaultDescription expectedFault = operation.Faults.Find(Service.FaultAction);
                        if (expectedFault == null || expectedFault.DetailType != typeof(string))
                        {
                            throw new InvalidOperationException("Operation must have FaultContract(typeof(string))");
                        }
                    }
                }
            }
        }
        static Binding GetBinding()
        {
            BasicHttpBinding result = new BasicHttpBinding();
            return result;
        }
        static void Main(string[] args)
        {
            string baseAddress = "http://localhost:8000/Service";
            ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
            host.AddServiceEndpoint(typeof(ITestServer), GetBinding(), "");
            host.Description.Behaviors.Add(new MyServiceBehavior());
            host.Open();
            Console.WriteLine("Host opened");
            Console.ReadLine();
            host.Close();
        }
    }

并且服务器的枚举类型中没有Test。

客户端调用时,会得到如下异常信息:

以上代码引用自此link;

更新:

我们可以通过serException.TargetSite.Name判断是否发生了枚举类型序列化异常:

if (serException != null&& serException.TargetSite.Name== "ReadEnumValue")
                        {
                            string detail = String.Format("{0}: {1}", serException.GetType().FullName, serException.Message);
                            FaultException<string> faultException = new FaultException<string>(detail, new FaultReason("CarBrand should be BMW, Honda or Tesla"));
                            MessageFault messageFault = faultException.CreateMessageFault();
                            fault = Message.CreateMessage(version, messageFault, Service.FaultAction);
                        }

如何在配置文件中应用MyServiceBehavior,可以参考这个link

您还可以使用 Attribute 将 MyServiceBehavior 应用于服务:

public class MyServiceBehavior : Attribute,IServiceBehavior
            {...