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

Posted

技术标签:

【中文标题】如何设置 WCF 自定义反序列化错误消息【英文标题】:How set a WCF custom deserialization error message 【发布时间】:2021-03-08 22:26:37 【问题描述】:

我正在创建一个 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 调用我的 Web 服务并将“Mercedes”作为 CarBrand 的值,他将收到一些错误消息,指出无法反序列化 CarBrand... 是否可以将此错误消息自定义为例如说“CarBrand 应该是 BMW、Honda 或 Tesla”?

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

谢谢

【问题讨论】:

【参考方案1】:

您可以使用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
            ...

【讨论】:

此自定义故障处理程序将用于所有反序列化错误,无论它是否与枚举相关。您需要找到一种方法来识别错误是否与枚举有关,然后才发送自定义错误。 @ding-peng 感谢您的帮助,您知道如何限制枚举的自定义错误吗?您还知道如何将 MyServiceBehavior 直接添加到 web.config(没有 Main(string[] args))吗? 这对消息中的所有枚举仍然有效,并且可能导致混淆。您需要使故障更通用并通过获取枚举类型来找到可能的值。评论框太小,无法举例。但我仍然相信这是应该在客户端处理的事情。数据协定和/或 wsdl 为您提供了正确的值,因此可以在调用服务器之前对其进行检查。 @DingPeng 感谢您更新您的回复,我可以测试您的代码并且它显示了预期的消息,但是是否可以使其更通用以便能够为每个代码显示特定消息枚举?例如,如果我有一个枚举 VehicleBrand 和一个枚举 ClothBrand,我希望能够为每个枚举显示不同的故障消息。你知道是否可以这样做吗? 我在@DingPeng 提供的解决方案中添加了一个建议。但它需要在它变得可见之前获得批准。【参考方案2】:

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

的消息
<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 在源处验证传入消息。

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

【讨论】:

以上是关于如何设置 WCF 自定义反序列化错误消息的主要内容,如果未能解决你的问题,请参考以下文章

如何在 WCF 中使用自定义序列化或反序列化来强制在 datacontact 的每个属性上创建一个新实例?

如何使用 DataContractSerializer 从文件中反序列化 WCF 肥皂响应消息?

在android studio中反序列化操作wcf的请求消息体时出错

WCF - 反序列化时控制命名空间

WCF 和 .NET 5.0:字节数组反序列化问题

WCF 错误“对象图中可以序列化或反序列化的最大项目数为 '65536'”