反序列化以前在序列化时包含值的空字段

时间:2012-10-10 13:16:27

标签: c# xml field deserialization nullable

我已阅读很多有关可空字段反序列化的帖子,但未遇到以下情况:

  1. 使用包含值的可空字段序列化对象(“nil”属性未添加到节点,因为它包含值)。
  2. 从xml中的可空字段中删除值(这通过客户端处理发生)。
  3. 反序列化xml。
  4. 步骤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中的每个引用类型。
    5. 注意:简单地将“nil = true”添加到具有空值的所有节点将不起作用,因为序列化程序将为不能为null的值类型引发错误。

      [编辑]代码示例:

      示例数据类

          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>
      

1 个答案:

答案 0 :(得分:0)

我提出的解决方案非常类似于原始问题中描述的攻击计划。

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

    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;
     }
  }