.NET WCF SOAP客户端如何处理Java BigDecimal的科学计数法输出?
解决WCF解析科学计数法Decimal字段的序列化错误问题
针对你遇到的Java供应商返回科学计数法格式Decimal字段导致.NET解析崩溃的问题,以下是几种无需手动修改生成代码的解决方案:
方案一:自定义XmlReader全局拦截解析逻辑
通过创建自定义XmlReader重写Decimal解析方法,全局处理所有科学计数法格式的字符串转换,无需修改生成的WCF客户端代码,适合批量处理所有Decimal字段。
1. 实现自定义XmlReader
using System.Globalization; using System.IO; using System.Xml; public class ScientificDecimalXmlReader : XmlTextReader { public ScientificDecimalXmlReader(TextReader reader) : base(reader) { } // 重写Decimal读取方法,优先处理科学计数法格式 public override decimal ReadElementContentAsDecimal() { string rawValue = ReadElementContentAsString(); if (!string.IsNullOrEmpty(rawValue) && decimal.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal parsedValue)) { return parsedValue; } // 解析失败时回退到默认逻辑 return base.ReadElementContentAsDecimal(); } // 补充重写其他可能读取Decimal的方法 public override decimal ReadContentAsDecimal() { string rawValue = ReadContentAsString(); if (!string.IsNullOrEmpty(rawValue) && decimal.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal parsedValue)) { return parsedValue; } return base.ReadContentAsDecimal(); } }
2. 实现WCF端点行为替换XmlReader
using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; using System.ServiceModel.Channels; using System.Xml; using System.IO; public class ScientificDecimalEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { // 设置XmlReader配额避免大内容解析报错 bindingParameters.Add(new XmlReaderQuotas { MaxDepth = 32, MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue }); } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 给所有客户端操作替换自定义格式化器 foreach (var operation in clientRuntime.ClientOperations) { operation.Formatter = new ScientificDecimalMessageFormatter(operation.Formatter); } } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } } public class ScientificDecimalMessageFormatter : IClientMessageFormatter { private readonly IClientMessageFormatter _originalFormatter; public ScientificDecimalMessageFormatter(IClientMessageFormatter originalFormatter) { _originalFormatter = originalFormatter; } public object DeserializeReply(Message message, object[] parameters) { // 替换原始消息的XmlReader为自定义实现 using (XmlDictionaryReader originalReader = message.GetReaderAtBodyContents()) { string bodyXml = originalReader.ReadOuterXml(); var textReader = new StringReader(bodyXml); var customReader = new ScientificDecimalXmlReader(textReader); // 重新构造消息 var newMessage = Message.CreateMessage(message.Version, message.Headers.Action, customReader); newMessage.Headers.CopyHeadersFrom(message.Headers); newMessage.Properties.CopyProperties(message.Properties); return _originalFormatter.DeserializeReply(newMessage, parameters); } } public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) { // 请求序列化沿用原始逻辑 return _originalFormatter.SerializeRequest(messageVersion, parameters); } }
3. 给WCF客户端添加行为
var soapClient = new YourGeneratedSoapClient(); soapClient.Endpoint.Behaviors.Add(new ScientificDecimalEndpointBehavior()); // 正常调用服务即可 var response = soapClient.YourServiceMethod();
方案二:修改WSDL生成模板(T4)自动注入解析逻辑
如果需要长期维护,可自定义T4模板让svcutil或Visual Studio生成客户端代码时,自动为Decimal字段添加科学计数法解析逻辑,避免每次更新WSDL后手动修改。
核心思路
修改T4模板中生成Decimal属性的代码段,将原始的自动属性改为带字段转换的逻辑:
// 模板生成的代码示例(简化) private string _spendRaw; private decimal? _spend; [System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=17)] public string spend { get => _spendRaw; set { _spendRaw = value; if (value != null && decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec)) { _spend = dec; } else { _spend = null; } } } [System.Xml.Serialization.XmlIgnoreAttribute] public decimal? SpendValue => _spend;
使用时通过SpendValue获取强类型的Decimal值,原始字符串字段用于序列化。
方案三:XmlAttributeOverrides针对特定字段处理
如果仅少数字段有问题,可使用XmlAttributeOverrides替换特定字段的序列化类型,手动处理转换:
using System.Xml.Serialization; // 创建属性重写配置 var overrides = new XmlAttributeOverrides(); var attrs = new XmlAttributes(); // 将spend字段临时映射为string类型 attrs.XmlElements.Add(new XmlElementAttribute(typeof(string)) { IsNullable = true, Order = 17 }); overrides.Add(typeof(YourGeneratedResponseClass), "spend", attrs); // 创建带重写配置的XmlSerializer var serializer = new XmlSerializer(typeof(YourGeneratedResponseClass), overrides); // 解析后手动转换字段 var response = (YourGeneratedResponseClass)serializer.Deserialize(/* 响应流 */); if (response.spend != null && decimal.TryParse(response.spend, NumberStyles.Float, CultureInfo.InvariantCulture, out var spendValue)) { // 使用spendValue }
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自定义XmlReader | 全局批量处理,无需修改生成代码,一劳永逸 | 需要编写WCF行为代码,调试复杂度稍高 |
| 修改T4模板 | 生成代码自动带处理逻辑,长期维护成本低 | 需要掌握T4模板编写,模板修改有一定学习成本 |
| XmlAttributeOverrides | 针对特定字段处理,灵活度高 | 字段较多时配置繁琐,WSDL更新后需重新核对配置 |
注意:避免将Decimal改为Double,现金金额使用Double会因浮点精度问题导致计算误差,必须保留Decimal类型。
内容的提问来源于stack exchange,提问作者Keith
相关产品推荐
相关产品推荐

