2012-10-10 44 views
3

我已经阅读了有关可空领域的反序列化的许多职位,但还没有碰到过下面的情况下运行空字段:反序列化以前包含的值,当它被序列化

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

由于序列化程序不会将可空字段的空值视为空值(因为未指定“nil = true”),所以步骤3将引发错误。它会尝试将该值转换为该字段的数据类型(例如: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的列表中的所有节点2 具有空值。
  4. 使用递归处理#2中的每个引用类型。

注意:只需将“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> 
+0

你可以改变数据对象的'Guid'属性为可为null的'Guid?'类型吗?编辑:在setter中,如果传递了'null'值,则可以将任何默认'Guid'(我猜'Guid.Empty')分配给后台字段。 –

+0

我所描述的场景实际上是使用可空的Guid类型。问题是没有nil =“true”,序列化器认为空值是一个有效的Guid,而不是空值。当指定“nil = true”时,反序列化null值可以正常工作。 –

+0

如果值为空,它应该不是您的客户端应用程序的责任,将nil = true属性添加到xml中?在将数据发送到服务器之前,您用什么来序列化数据? – coalvilledave

回答

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