.NET 4.6 Windows服务MTOM传输下修改Content-Type HTTP头的方法
我在基于.NET 4.6的Windows服务中,使用VS2022为某个WSDL服务创建了服务引用,原本运行正常。现在需要在HTTP请求头的Content-Type中添加一个参数,但因为使用了MTOM传输,尝试修改Content-Type头时遇到了问题。
我试过实现继承自IEndpointBehavior的自定义行为,以及对应的IClientMessageInspector自定义检查器,在BeforeSendRequest方法中操作,但发现此时无法读取到完整的Content-Type头——因为MTOM消息的boundary元素是在这个调用之后才生成的。
当前的Content-Type头:
multipart/related;type="application/xop+xml";charset=UTF-8;start-info="application/soap+xml";boundary="uuid:00361b85-692d-43a1-9f17-57d70bbe26df+id=1"
需要添加的内容:
action="{action}";
这种需求是否可行?
现有代码片段
绑定与客户端初始化
WSHttpBinding wsHttpBinding = new WSHttpBinding(); wsHttpBinding.Security.Mode = SecurityMode.Transport; wsHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate; wsHttpBinding.MessageEncoding = WSMessageEncoding.Mtom; Client = new ServiceClient(wsHttpBinding, new EndpointAddress(EndPoint));
添加自定义行为
Client.Endpoint.Behaviors.Add(new CustomBehavior(action));
自定义行为与检查器实现
public class CustomBehavior : IEndpointBehavior { private string action = null; public CustomBehavior(string action, byte[] file) { this.action = action; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(new CustomInspector(this.action)); } } private class CustomInspector : IClientMessageInspector { private string action; public CustomInspector(string action) { this.action = action; } public void AfterReceiveReply(ref Message reply, object correlationState) { } public object BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty reqProps = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; if (reqProps != null) { reqProps = new HttpRequestMessageProperty(); } reqProps.Headers.Add("Content-Type", $"multipart/related;type=\"application/xop+xml\";charset=UTF-8;start-info=\"application/soap+xml\";action=\"{action}\";boundary=\"uuid:00361b85-692d-43a1-9f17-57d70bbe26df+id=1\";\"); request.Properties[HttpRequestMessageProperty.Name] = reqProps; return null; } }
服务调用代码
using (OperationContextScope scope = new OperationContextScope((Client as DocumentRepository_PortTypeClient).InnerChannel)) { // 尝试在此读取头但为空 var obj = OperationContext.Current.OutgoingMessageProperties; response = Client.call(request); }
可行,但不能直接在IClientMessageInspector的BeforeSendRequest中修改Content-Type——因为MTOM的boundary确实是在这个阶段之后由绑定组件生成的,手动指定boundary会导致和实际生成的不一致,引发请求错误。
正确的做法是通过自定义绑定元素拦截MTOM消息的Content-Type生成过程,具体实现步骤如下:
- 创建自定义MTOM绑定元素
public class CustomMtomBindingElement : MtomMessageEncodingBindingElement { private string _action; public CustomMtomBindingElement(string action) { _action = action; } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { context.BindingParameters.Add(new ActionHeaderSetting(_action)); return base.BuildChannelFactory<TChannel>(context); } public override BindingElement Clone() { return new CustomMtomBindingElement(_action); } public override T GetProperty<T>(BindingContext context) { return context.GetInnerProperty<T>(); } } public class ActionHeaderSetting { public string Action { get; } public ActionHeaderSetting(string action) { Action = action; } }
- 创建自定义MTOM消息编码器
public class CustomMtomMessageEncoder : MtomMessageEncoder { private string _action; public CustomMtomMessageEncoder(MtomMessageEncoder innerEncoder, string action) : base(innerEncoder.ContentType, innerEncoder.MediaType, innerEncoder.MessageVersion) { _action = action; } public override void WriteMessage(Message message, Stream stream) { // 先执行原逻辑生成包含正确boundary的Content-Type base.WriteMessage(message, stream); // 给已生成的Content-Type添加action参数 if (!this.ContentType.Contains("action=")) { this.ContentType = $"{this.ContentType};action=\"{_action}\""; } } }
- 创建自定义编码器工厂
public class CustomMtomEncoderFactory : MessageEncoderFactory { private MessageEncoderFactory _innerFactory; private string _action; public CustomMtomEncoderFactory(MessageEncoderFactory innerFactory, string action) { _innerFactory = innerFactory; _action = action; } public override MessageEncoder Encoder => new CustomMtomMessageEncoder((MtomMessageEncoder)_innerFactory.Encoder, _action); public override MessageVersion MessageVersion => _innerFactory.MessageVersion; }
- 修改客户端绑定初始化逻辑
// 创建自定义MTOM绑定元素并复制原配置 var customMtomBindingElement = new CustomMtomBindingElement(action); customMtomBindingElement.MaxReadPoolSize = wsHttpBinding.MaxReceivedMessageSize; customMtomBindingElement.MaxWritePoolSize = wsHttpBinding.MaxBufferPoolSize; customMtomBindingElement.MessageVersion = wsHttpBinding.MessageVersion; customMtomBindingElement.WriteEncoding = Encoding.UTF8; // 构建自定义绑定 var customBinding = new CustomBinding(); customBinding.Elements.Add(customMtomBindingElement); // 添加原绑定的HTTPS传输元素 customBinding.Elements.Add(wsHttpBinding.CreateBindingElements().First(b => b is HttpsTransportBindingElement)); // 使用自定义绑定创建客户端 Client = new ServiceClient(customBinding, new EndpointAddress(EndPoint));
这种方式会在MTOM自动生成正确的boundary之后,动态将action参数添加到Content-Type头中,既保证了boundary的正确性,又满足了需求。
内容的提问来源于stack exchange,提问作者Giangio

