【问题标题】:Deserializing a null field that previously contained a value when it was serialized反序列化之前在序列化时包含值的空字段
【发布时间】:2012-10-10 13:16:27
【问题描述】:

我已经阅读了许多关于可空字段反序列化的帖子,但没有遇到以下情况:

  1. 序列化具有包含值的可为空字段的对象(“nil”属性未添加到节点,因为它包含值)。
  2. 从 xml 中的可为空字段中删除值(这通过客户端处理发生)。
  3. 反序列化 xml。

第 3 步会引发错误,因为序列化程序不会将可空字段的空值视为空值(因为未指定“nil=true”)。相反,它会尝试将值转换为字段的数据类型(例如:Guid),但失败会导致错误消息因字段的数据类型而异。

在 Guid 的情况下,错误消息是:

    System.InvalidOperationException: There is an error in XML document ([line number], [column number]). ---> System.FormatException: Unrecognized Guid format.

我要注意的是,我们使用的序列化/反序列化方法是使用泛型的框架方法。

我正在寻找一种优雅而通用的解决方案。我能想到的唯一可行的通用解决方案如下:

  1. 将 xml 转换为 XDocument。
  2. 使用(低于预期的)反射来获取对象的所有引用类型的属性。
  3. 将“nil=true”属性添加到名称在 #2 的列表中找到的所有节点并且具有空值。
  4. 使用递归处理 #2 中的每个引用类型。

注意:简单地将“nil=true”添加到所有具有空值的节点将不起作用,因为序列化程序会为不能为空的值类型抛出错误。

[编辑]代码示例:

示例数据类

    public class DummyData
    {
        public Guid? NullableGuid { get; set; }
    }

Xml 发送给客户端

    <DummyData>
    <NullableGuid>052ec82c-7322-4745-9ac1-20cc4e0f142d</NullableGuid>
    </DummyData>

从客户端返回的Xml(错误)

    <DummyData>
    <NullableGuid></NullableGuid>
    </DummyData>

从客户端返回的Xml(期望的结果)

    <DummyData>
        <NullableGuid p2:nil="true" xmlns:p2="http://www.w3.org/2001/XMLSchema-instance"></NullableGuid>
    </DummyData>

【问题讨论】:

  • 你能把数据对象的Guid属性改成可以为空的Guid?类型吗?编辑:在设置器中,如果传递了 null 值,您可以将任何默认值 Guid(我猜是 Guid.Empty)分配给支持字段。
  • 我所描述的场景实际上是使用一个可为空的 Guid 类型。问题是如果没有 nil="true",序列化程序会认为空值是有效的 Guid,而不是空值。指定“nil=true”时,反序列化空值可以正常工作。
  • 如果值为空,是否应该由您的客户端应用程序将 nil=true 属性添加到 xml 中?在将数据发送到服务器之前,您使用什么来序列化数据?
  • 期望每个脚本编写者都处理这种情况会非常麻烦。此外,客户端不知道节点是引用类型还是值类型。将“nil=true”添加到值类型也会导致错误,因为值类型不能为空(如帖子末尾所述)。

标签: c# xml field deserialization nullable


【解决方案1】:

这是我提出的解决方案,与我在原始问题中描述的攻击计划非常相似。

免责声明:它不短,很可能没有涵盖所有反序列化场景,但似乎可以完成工作。

    public static T FromXml<T>(string xml)
    {
       string convertedXml = AddNilAttributesToNullableTypesWithNullValues(typeof(T), xml);
       var reader = new StringReader(convertedXml);
       var serializer = new XmlSerializer(typeof (T));
       var data = (T) serializer.Deserialize(reader);
       reader.Close();
       return data;
    }

    private static string AddNilAttributesToNullableTypesWithNullValues(Type type, string xml)
    {
        string result;

        if (!string.IsNullOrWhiteSpace(xml))
        {
            XDocument doc = XDocument.Parse(xml);

            if (doc.Root != null)
                AddNilAttributesToNullableTypesWithNullValues(doc.Root, type);

            result = doc.ToString();
        }
        else
            result = xml;

        return result;
    }

    private static void AddNilAttributesToNullableTypesWithNullValues(XElement element, Type type)
      {
         if (type == null)
            throw new ArgumentNullException("type");

         if (element == null)
            throw new ArgumentNullException("element");

         //If this type can be null and it does not have a value, add or update nil attribute
         //with a value of true.
         if (IsReferenceOrNullableType(type) && string.IsNullOrEmpty(element.Value))
         {
            XAttribute existingNilAttribute = element.Attributes().FirstOrDefault(a => a.Name.LocalName == NIL_ATTRIBUTE_NAME);

            if (existingNilAttribute == null)
               element.Add(NilAttribute);
            else
               existingNilAttribute.SetValue(true);
         }
         else
         {
            //Process all of the objects' properties that have a corresponding child element.
            foreach (PropertyInfo property in type.GetProperties())
            {
               string elementName = GetElementNameByPropertyInfo(property);

               foreach (XElement childElement in element.Elements().Where(e =>
                  e.Name.LocalName.Equals(elementName)))
               {
                  AddNilAttributesToNullableTypesWithNullValues(childElement, property.PropertyType);
               }
            }

            //For generic IEnumerable types that have elements that correspond to the enumerated type,
            //process the each element.
            if (IsGenericEnumerable(type))
            {
               Type enumeratedType = GetEnumeratedType(type);

               if (enumeratedType != null)
               {
                  IEnumerable<XElement> enumeratedElements = element.Elements().Where(e =>
                     e.Name.LocalName.Equals(enumeratedType.Name));

                  foreach (XElement enumerableElement in enumeratedElements)
                     AddNilAttributesToNullableTypesWithNullValues(enumerableElement, enumeratedType);
               }
            }
         }
      }

      private static string GetElementNameByPropertyInfo(PropertyInfo property)
      {
         string overrideElementName = property.GetCustomAttributes(true).OfType<XmlElementAttribute>().Select(xmlElementAttribute => 
            xmlElementAttribute.ElementName).FirstOrDefault();
         return overrideElementName ?? property.Name;
      }

      private static Type GetEnumeratedType(Type type)
      {
         Type enumerableType = null;

         Type[] types = type.GetGenericArguments();

         if (types.Length == 1)
            enumerableType = types[0];

         return enumerableType;
      }

      public static bool IsGenericEnumerable(Type type)
      {
         return type.IsGenericType && type.GetInterfaces().Any(i => 
            i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
      }

      private static bool IsReferenceOrNullableType(Type type)
      {
         return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
      }

      private const string NIL_ATTRIBUTE_NAME = "nil";
      private const string XML_SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance";

      private static XAttribute NilAttribute
      {
         get
         {
             if (_nilAttribute == null)
             {
                 XNamespace xmlSchemaNamespace = XNamespace.Get(XML_SCHEMA_NAMESPACE);
                 _nilAttribute = new XAttribute(xmlSchemaNamespace + NIL_ATTRIBUTE_NAME, true);
         }

        return _nilAttribute;
     }
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多