【问题标题】:How set a WCF custom deserialization error message如何设置 WCF 自定义反序列化错误消息
【发布时间】: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。

谢谢

【问题讨论】:

    标签: c# wcf soap data-annotations wcf-binding


    【解决方案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 在源处验证传入消息。

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

    【讨论】:

      猜你喜欢
      • 2011-09-09
      • 1970-01-01
      • 2022-08-23
      • 2015-06-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多