3

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

  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>
4

1 回答 1

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;
     }
  }
于 2012-10-11T11:54:59.840 回答