【问题标题】:Serializable Class serializes to Xml, but the same Xml won't deserializeSerializable Class 序列化为 Xml,但同一个 Xml 不会反序列化
【发布时间】:2012-11-21 14:15:21
【问题描述】:

我们从 WCF Web 服务返回 AuthenticateUserOutput 类的实例。

所以我们有这个方法:

public override AuthenticateUserOutput AuthenticateUser(AuthenticateUserInput AuthenticateUserInput)

AuthencateUserOutput 是自动生成的:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.5420")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="xxxx")]
public partial class AuthenticateUserOutput : WS2MethodOutput
{

    private bool authenticationResultField;

    private UserContext userContextField;

    /// <remarks/>
    public bool AuthenticationResult
    {
        get
        {
            return this.authenticationResultField;
        }
        set
        {
            this.authenticationResultField = value;
        }
    }

    /// <remarks/>
    public UserContext UserContext
    {
        get
        {
            return this.userContextField;
        }
        set
        {
            this.userContextField = value;
        }
    }
}

我们需要能够反序列化为AuthenticateUserOutput,但它不起作用。

作为测试,我实例化了一个AuthenticateUserOutput,将其序列化并尝试反序列化。

InvalidOperationException 失败。

这是序列化的 Xml:

<?xml version="1.0" encoding="utf-16"?>
<AuthenticateUserOutput xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="xxxx">
  <Response xmlns="xxxx">
    <IsValid>true</IsValid>
    <Success>true</Success>
  </Response>
  <AuthenticationResult xmlns="xxxx">true</AuthenticationResult>
  <UserContext xmlns="xxxx">
  </UserContext>
</AuthenticateUserOutput>

下面是序列化和反序列化代码:

    public static string ToXml(this object input)
    {
        string output;
        XmlSerializer serializer = new XmlSerializer(input.GetType());
        StringBuilder sb = new StringBuilder();
        using (TextWriter textWriter = new StringWriter(sb))
        {
            serializer.Serialize(textWriter, input);
        }
        output = sb.ToString();
        return output;
    }

还有:

    public static T FromXml<T>(this string input)
    {
        T output;

        XmlSerializer serializer = new XmlSerializer(typeof(T));

        output = (T)serializer.Deserialize(new StringReader(input));

        return output;
    }

异常的具体细节:

System.InvalidOperationException occurred
  Message="<AuthenticateUserOutput xmlns='xxxx'> was not expected."
  Source="qkxd8dd-"
  StackTrace:
       at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderAuthenticateUserOutput.Read31_AuthenticateUserOutput()
  InnerException: 

所以它不能反序列化它自己的序列化 Xml。

有人知道为什么吗?

谢谢,

J1M.

更新:这是测试代码:

        AuthenticateUserOutput test = new AuthenticateUserOutput();

        test.AuthenticationResult = true;
        test.Response = new ResponseType();
        test.Response.Exception = null;
        test.Response.IsValid = true;
        test.Response.Success = true;
        test.Response.ValidationErrors = null;
        test.UserContext = new UserContext();

        string serializedXml = test.ToXml();

        AuthenticateUserOutput deserializedString = serializedXml.FromXml<AuthenticateUserOutput>();

UPDATE2:感谢 Mark Gravell,反序列化器的工作原理

OK, I forgot I'd added this to get the namespaces to come out correctly when de-serializing:

public partial class WS2MethodInput
{
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces _xmlns;

    /// <summary>
    /// Constructor for WS2MethodInput that sets up default namespaces
    /// </summary>
    public WS2MethodInput()
    {
        _xmlns = new XmlSerializerNamespaces();
        _xmlns.Add("", "xxxx");
    }
}

这样,序列化的 Xml 就像它在此消息的顶部一样。 没有它,反序列化程序可以工作,但 AuthenticateUserOutput 缺少命名空间:

<?xml version="1.0" encoding="utf-16"?>
<AuthenticateUserOutput xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="xxxx">
  <Response xmlns="xxxx">
    <IsValid>true</IsValid>
    <Success>true</Success>
  </Response>
  <AuthenticationResult xmlns="xxxx">true</AuthenticationResult>
  <UserContext xmlns="xxxx">
  </UserContext>
</AuthenticateUserOutput>

注意AuthenticateUserOutput末尾的xmlns="xxxx"

问题是,现在我不能在没有其他代码的情况下将该 Xml 与我们的其他代码一起使用:

1) 将其加载到 XDocument 中并添加命名空间并在需要反序列化时将其删除 2)对正则表达式的字符串替换或其他东西做同样的事情

这两个我都不喜欢。事实上,这太可怕了! 8X

【问题讨论】:

  • TFromXml 是什么,原始对象的GetType() 是什么?它们是一样的吗?
  • 我将添加测试代码,T 是 AuthenticateUserOutput。我会为很多方法这样做,所以我从编写 To/From Xml 扩展开始
  • 哦,是的,typeof(T) == input.GetType()
  • 你的测试代码基本上对我有用;我不得不稍微调整一下才能让它编译(添加一些类,删除响应)
  • 谢谢,这让我可以开始工作了。我弄清楚了原因,我将在帖子中添加更多内容。然而,问题是由我添加并忘记发布的另一个问题的解决方案引起的。但是,如果我删除该解决方案,另一个问题又回来了。忍受我。

标签: c# xmlserializer invalidoperationexception


【解决方案1】:

问题在于将 XmlSerializerNamespaces 设置为在根元素上强制使用 xmlns。

把它拿走,它就起作用了。

但是,现在我无法在其他任何地方使用序列化的 Xml,因为它缺少命名空间。

我将开始一个新的问题。

谢谢,

J1M.

更新:

好的,我想通了,所以我不打算写新问题了。

给定以下 XSD:

<xs:schema xmlns="urn:www-test-com:testservice" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:www-test-com:testservice" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xs:element name="AuthenticateUserInput">
        <xs:annotation>
            <xs:documentation>Used to provide user credentials to the AuthenticateUser method</xs:documentation>
        </xs:annotation>
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Username" type="xs:string"/>
                <xs:element name="Password" type="xs:string"/>
                <xs:element name="Method" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

svcutil 会生成类似的东西,尽管我想有很多工具会为 XSD 吐出类似的东西:

[Serializable()]
[XmlType(AnonymousType = true, Namespace = "urn:www-test-com:testservice")]
public class AuthenticateUserInput
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string Method { get; set; }
}

现在,使用“默认”XmlSerializer 代码:

    static string ToXml(object input)
    {
        string output;

        XmlSerializer serializer = new XmlSerializer(input.GetType());
        StringBuilder sb = new StringBuilder();

        using (XmlWriter xw = XmlWriter.Create(sb))
        {
            serializer.Serialize(xw, input);
        }
        output = sb.ToString();
        return output;
    }

您会得到以下 Xml:

<AuthenticateUserInput xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Username xmlns="urn:www-test-com:testservice">sa</Username>
    <Password xmlns="urn:www-test-com:testservice">Password1</Password>
    <Method xmlns="urn:www-test-com:testservice">Plain</Method>
</AuthenticateUserInput>

不符合架构(验证失败,这就是目前正在扼杀我们的原因)

好的,所以,我找到了将 [XmlType] 更改为 [XmlRoot] 的参考,并且粗略的测试有效,如果我更改生成的类,我现在会得到以下一致的 Xml:

<AuthenticateUserInput xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:www-test-com:testservice">
    <Username>sa</Username>
    <Password>Password1</Password>
    <Method>Plain</Method>
</AuthenticateUserInput>

但我不想手动更改所有自动生成的类。所以过了一段时间我找到了以下解决方案:

    static string ToXml(object input)
    {
        string output;

        XmlSerializer serializer = CreateSerializer(input.GetType());
        StringBuilder sb = new StringBuilder();

        var settings = new XmlWriterSettings() { OmitXmlDeclaration = true, Encoding =  Encoding.UTF8, Indent = true };

        using (XmlWriter xw = XmlWriter.Create(sb, settings))
        {
            serializer.Serialize(xw, input);
        }
        output = sb.ToString();
        return output;
    }

    static T FromXml<T>(string input)
    {
        T output;

        XmlSerializer serializer = CreateSerializer(typeof(T));

        output = (T)serializer.Deserialize(new StringReader(input));

        return output;
    }

    private static XmlSerializer CreateSerializer(Type incomingType)
    {
        XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
        XmlAttributes newAttributes = new XmlAttributes();

        newAttributes.XmlRoot = new XmlRootAttribute();
        newAttributes.XmlRoot.Namespace = ((XmlTypeAttribute)incomingType.GetCustomAttributes(typeof(XmlTypeAttribute), true)[0]).Namespace;
        attrOverrides.Add(incomingType, newAttributes);

        XmlSerializer serializer = new XmlSerializer(incomingType, attrOverrides);
        return serializer;
    }

如您所见,您可以创建一个XmlAttributeOverrides 并添加一个XmlAttributes,而不是仅使用XmlSerializer 的默认构造函数,并在其上实例化XmlRoot 属性。 为了使这个更好和通用,它使用((XmlTypeAttribute)incomingType.GetCustomAttributes(typeof(XmlTypeAttribute), true)[0]).Namespace 将命名空间从传入类型中剔除,如果类型没有该属性,则会失败,因此此解决方案需要更多工作,但您了解一般概念。

问候,

J1M

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 2011-06-24
    • 1970-01-01
    • 2011-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多