如何在C#中修改WCF客户端请求的XML命名空间前缀?
解决方案
1. 直接修改自动生成的Reference.cs(快速但要注意维护)
这是最直接的方法,但有个小坑:每次更新服务引用,修改会被覆盖,所以如果后续不需要频繁更新WSDL,可以试试:
- 找到VS生成的
Reference.cs文件,搜索XmlSerializerNamespaces相关代码,一般是在服务客户端的构造方法或者请求序列化的地方,把默认的前缀映射换掉:
原来的代码大概是这样:
改成你需要的:var namespaces = new XmlSerializerNamespaces(); namespaces.Add("s", "http://schemas.xmlsoap.org/soap/envelope/"); namespaces.Add("h", "http://foo.foo.com/Hello");var namespaces = new XmlSerializerNamespaces(); namespaces.Add("soapenv", "http://schemas.xmlsoap.org/soap/envelope/"); namespaces.Add("foo", "http://foo.foo.com/Hello"); - 另外,还要检查所有带
XmlElement、XmlType特性的类和属性,如果里面指定了Namespace对应的前缀相关配置,也要同步调整,确保序列化时所有元素都用新前缀。
2. 实现自定义消息格式化器(推荐,可长期维护)
这个方法不用动自动生成的代码,通过拦截SOAP请求并修改XML内容来替换前缀,稳定性更高:
- 先创建一个自定义的消息格式化类,继承
SoapClientMessageFormatter(或者实现IClientMessageFormatter接口),重写SerializeRequest方法:public class CustomSoapFormatter : SoapClientMessageFormatter { public CustomSoapFormatter(OperationDescription operation, bool isRequest) : base(operation, isRequest) { } public override Message SerializeRequest(MessageVersion messageVersion, object[] parameters) { // 先用默认逻辑生成原始消息 var originalMsg = base.SerializeRequest(messageVersion, parameters); var buffer = originalMsg.CreateBufferedCopy(int.MaxValue); var clonedMsg = buffer.CreateMessage(); // 读取消息XML内容 var reader = clonedMsg.GetReaderAtBodyContents(); var xmlContent = reader.ReadOuterXml(); // 替换所有前缀和命名空间声明 xmlContent = xmlContent.Replace("<s:", "<soapenv:") .Replace("</s:", "</soapenv:") .Replace("<h:", "<foo:") .Replace("</h:", "</foo:") .Replace("xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"", "xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"") .Replace("xmlns:h=\"http://foo.foo.com/Hello\"", "xmlns:foo=\"http://foo.foo.com/Hello\""); // 重新构建消息并返回 var newMsg = Message.CreateMessage(messageVersion, clonedMsg.Headers.Action, XmlReader.Create(new StringReader(xmlContent))); foreach (var header in clonedMsg.Headers) { newMsg.Headers.Add(header); } return newMsg; } } - 再创建一个端点行为类,用来把自定义格式化器注入到客户端:
public class CustomFormatterBehavior : IEndpointBehavior { public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { foreach (var operation in clientRuntime.ClientOperations) { operation.Formatter = new CustomSoapFormatter(operation.OperationDescription, true); } } // 其他IEndpointBehavior接口方法留空实现即可 public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } } - 最后在创建客户端实例时添加这个行为:
public GetYaddaYaddaResponseType CheckConnection() { Yadda client = new Yadda(); // 添加自定义格式化器行为 client.Endpoint.Behaviors.Add(new CustomFormatterBehavior()); var msgRequest = new GetYaddaYaddaResponseType { ConnectionConfirmationRequest = new ConfirmConnectRequestType() }; List<Statement> statementList = new List<Statement> { new Statement { Name = "Yo", Value = "123" }, new Statement { Name = "Hey", Value = "123" }, }; var header = new StatementHeader { StatementList = statementList.ToArray() }; var response = client.GetYaddaYaddaMessage(ref header, msgRequest); return response; }
3. 修改WSDL文件后重新生成服务引用(一劳永逸)
既然SoapUI加载你的本地WSDL能生成正确前缀,说明WSDL里本身就定义了soapenv和foo的前缀映射!VS自动生成服务引用时会忽略这些自定义前缀,改用默认的s、h,所以你可以:
- 打开本地的.wsdl文件,找到根节点
<definitions>的命名空间声明,把原来的s和h前缀改成soapenv和foo:
原声明:
修改后:<definitions xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:h="http://foo.foo.com/Hello" ...><definitions xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foo.foo.com/Hello" ...> - 同时检查WSDL里所有引用这些前缀的地方(比如
<soap:binding>、<wsdl:input>等节点),把s和h都替换成新前缀; - 用修改后的WSDL重新生成服务引用,VS这次就会按照WSDL里的前缀生成代码,不用再手动调整了!
关于SoapUI能生成正确前缀的原因
SoapUI在解析WSDL时,会严格遵循WSDL文件里定义的命名空间前缀映射,不会像VS那样自动替换成默认的s、h。你打开你的本地WSDL文件就能看到,里面已经明确指定了soapenv和foo作为对应命名空间的前缀,所以SoapUI直接沿用了这些配置,而VS的服务引用生成工具默认做了简化,才导致前缀不符。
内容的提问来源于stack exchange,提问作者Shawn R.
相关产品推荐
相关产品推荐

