如何设置 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
{...
我正在创建一个 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
{...