在 WCF 中为复杂类型的元素设置命名空间

Set namespace for element in complextype in WCF

我已经使用 svcutil 为外部 Web 服务生成了一个客户端。这是处理服务调用的代码

XmlDocument doc = new XmlDocument();
doc.LoadXml(payload.generate());

TestRequest testMessage = new TestRequest();
XmlElement[] elem = new XmlElement[1];
elem[0] = doc.DocumentElement;
testMessage.Items = elem;
TestResponse response = client.Test(Operation, Version, 0, testMessage);

以及 svcutil 生成的代码:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://provider/", ConfigurationName="Tests.Proxies.TestService")]
public interface TestService
{
    [System.ServiceModel.OperationContractAttribute(Action="http://provider/Test", ReplyAction="*")]
    [System.ServiceModel.XmlSerializerFormatAttribute()]
    [return: System.ServiceModel.MessageParameterAttribute(Name="Response")]
    Tests.Proxies.TestResponse Test(string Operation, string Version, long CheckSum, Tests.Proxies.TestRequest Request);
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "4.0.30319.18020")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://provider/")]
public partial class TestRequest
{
    private System.Xml.XmlElement[] itemsField;

    private string[] textField;

    [System.Xml.Serialization.XmlAnyElementAttribute(Order=0)]
    public System.Xml.XmlElement[] Items
    {
        get
        {
            return this.itemsField;
        }
        set
        {
            this.itemsField = value;
        }
    }

    ...
}

有效负载由第三方库生成:

<payload>
...
</payload>

但是在调用客户端时,序列化器将其修改为空命名空间:

<payload xmlns="">
...
</payload>

导致测试失败,因为服务希望它在 "urn:org:ns" 命名空间中合格,所以我尝试使用以下方法手动设置它:

doc.DocumentElement.SetAttribute("xmlns", "urn:org:ns");

但这会引发以下异常:

CommunicationException:
{"Error serializing message : 'Error generating XML document."}
{"Prefix '' is already bound to namespace '' and cannot be changed to 'urn:org:ns'.
Parameter name: prefix"}

[System.Xml.Serialization.XmlAnyElementAttribute(Namespace="urn:org:ns")]添加到Items 属性对序列化没有任何影响。

以防万一,这是 wsdl 的相关部分:

<s:element name="Test">
    <s:complexType>
        <s:sequence>
            <s:element minOccurs="0" name="Operation" type="s:string"/>
            <s:element minOccurs="0" name="Version" type="s:string"/>
            <s:element minOccurs="0" name="CheckSum" type="s:long"/>
            <s:element name="TestRequest">
                <s:complexType mixed="true">
                    <s:choice maxOccurs="unbounded" minOccurs="0">
                        <s:any processContents="lax"/>
                    </s:choice>
                </s:complexType>
            </s:element>
        </s:sequence>
    </s:complexType>
</s:element>

我正在尝试做的事情是否可行?

我解决了它,结果发现它与 WCF 本身没有太大关系,但与 XML 处理有关。如 this answer 中所述,如果没有显式分配一个节点,它将始终分配给默认名称空间,并且这只能在创建时完成,因此我编写了一个递归函数来完成此操作。这是一个快速而肮脏的解决方案,但现在可以解决问题。如果您打算使用它,请当心,它可能不适合您的用例。

XmlDocument doc = new XmlDocument();
doc.LoadXml(payload.generate());
XmlElement newRootNode = RecursiveCopy(doc.DocumentElement, doc, "urn:org:ns");

public XmlElement RecursiveCopy(XmlNode node, XmlDocument doc, string targetNamespace) 
{  
    XmlElement nodeCopy = doc.CreateElement(node.LocalName, targetNamespace);
    foreach (XmlNode child in node.ChildNodes) 
    {
        if (child.NodeType == XmlNodeType.Element)
        {
            nodeCopy.AppendChild(RecursiveCopy(child, doc, targetNamespace);
        }
        else if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA) 
        {
            XmlNode newNode = doc.CreateNode(child.NodeType, child.LocalName, targetNamespace);
            newNode.Value = child.Value;
            nodeCopy.AppendChild(newNode);
        }
    }
    return nodeCopy;
}