.NET WCF:MTOM消息读取器创建失败问题排查求助
你遇到的问题核心在于成功响应的Content-ID格式不符合WCF MTOM消息编码器的预期规范,而错误响应刚好符合格式要求,所以能正常解析。
关键差异对比
我们直接对比两种响应的Content-ID格式:
- 成功响应的Content-ID:
Content-ID: a35854a.245cd7c5.5f.180a95c85a5.N7ff5(没有包裹尖括号<>) - 错误响应的Content-ID:
Content-ID: <http://tempuri.org/0>(正确包裹了尖括号) - 你发送的请求里的Content-ID也都是带尖括号的,比如
<http://tempuri.org/0>、<http://tempuri.org/1/637877128927000384>
为什么会抛出异常?
WCF的MTOM消息编码器严格遵循RFC 2392(Content-ID和Message-ID的规范),该规范要求Content-ID的值必须用尖括号<>包裹。当服务返回的成功响应没有给Content-ID加尖括号时,WCF的解析器无法正确识别这个MIME部分的标识,就会抛出MIME part with Content-ID <xxx> not found的异常——注意异常信息里自动给Content-ID补了尖括号,但实际响应里并没有,所以自然找不到匹配的MIME部分。
而错误响应的Content-ID符合规范,解析器能正常定位到对应的XML内容,顺利完成反序列化。
解决方案
方案1:修改服务端(优先推荐)
如果有权限修改Web服务的代码,让它返回的MTOM响应中,Content-ID的值严格用尖括号包裹,比如把Content-ID: a35854a.245cd7c5.5f.180a95c85a5.N7ff5改成Content-ID: <a35854a.245cd7c5.5f.180a95c85a5.N7ff5>,这样WCF就能正常解析响应了。
方案2:客户端自定义消息处理(无法修改服务端时)
如果无法修改服务端,你可以在客户端添加一个自定义消息检查器,在WCF解析响应之前,手动修正Content-ID的格式,给它补上尖括号。
以下是VB.NET的示例代码:
Imports System.ServiceModel Imports System.ServiceModel.Channels Imports System.ServiceModel.Dispatcher Imports System.Text.RegularExpressions Public Class FixMtomContentIdInspector Implements IClientMessageInspector Public Sub AfterReceiveReply(ByRef reply As Message, correlationState As Object) Implements IClientMessageInspector.AfterReceiveReply ' 检查是否是MTOM响应 If reply.Properties.ContainsKey(MtomMessageEncoder.MtomContentTypeProperty) Then ' 将消息转换为可修改的Buffer Dim buffer As MessageBuffer = reply.CreateBufferedCopy(Integer.MaxValue) Dim originalMsg As Message = buffer.CreateMessage() ' 获取原始的MTOM内容并转为字符串 Dim mtomContent As Byte() = originalMsg.GetBody(Of Byte())() Dim mtomString As String = Encoding.UTF8.GetString(mtomContent) ' 修正Content-ID:给没有尖括号的Content-ID添加尖括号 Dim correctedMtom As String = Regex.Replace(mtomString, "Content-ID: ([^<>\r\n]+)", "Content-ID: <$1>", RegexOptions.IgnoreCase) ' 创建修正后的消息 Dim correctedBuffer As ArraySegment(Of Byte) = New ArraySegment(Of Byte)(Encoding.UTF8.GetBytes(correctedMtom)) Dim newMsg As Message = Message.CreateMessage(originalMsg.Version, Nothing, correctedBuffer) ' 复制原始消息的属性 For Each prop In originalMsg.Properties newMsg.Properties(prop.Key) = prop.Value Next ' 替换原消息 reply = newMsg End If End Sub Public Function BeforeSendRequest(ByRef request As Message, channel As IClientChannel) As Object Implements IClientMessageInspector.BeforeSendRequest ' 发送请求时无需处理,直接返回null Return Nothing End Function End Class ' 扩展类,用于向客户端端点添加消息检查器 Public Class FixMtomContentIdBehavior Implements IEndpointBehavior Public Sub AddBindingParameters(endpoint As ServiceEndpoint, bindingParameters As BindingParameterCollection) Implements IEndpointBehavior.AddBindingParameters ' 无需处理 End Sub Public Sub ApplyClientBehavior(endpoint As ServiceEndpoint, clientRuntime As ClientRuntime) Implements IEndpointBehavior.ApplyClientBehavior clientRuntime.MessageInspectors.Add(New FixMtomContentIdInspector()) End Sub Public Sub ApplyDispatchBehavior(endpoint As ServiceEndpoint, endpointDispatcher As EndpointDispatcher) Implements IEndpointBehavior.ApplyDispatchBehavior ' 客户端行为,无需处理 End Sub Public Sub Validate(endpoint As ServiceEndpoint) Implements IEndpointBehavior.Validate ' 无需处理 End Sub End Class
然后在创建客户端代理时,添加这个行为:
Dim client As New YourServiceClient(myBinding, New EndpointAddress("your-service-url")) client.Endpoint.Behaviors.Add(New FixMtomContentIdBehavior())
快速验证方法
你可以先用Fiddler修改成功响应的Content-ID,给它加上尖括号,再让客户端接收这个修改后的响应,应该就能正常解析了——这可以快速验证问题确实出在Content-ID的格式上。
内容的提问来源于stack exchange,提问作者starkspc

