【问题标题】:Custom DataContractSerializer自定义 DataContractSerializer
【发布时间】:2017-02-25 19:32:14
【问题描述】:

我想序列化一个带有 DataMember 属性的对象,以便在某些属性上被忽略。

假设我有自定义属性 MyIgnoreDataMember。

我希望用它标记的属性对于我的自定义 DataContractSerializer 不可见,但对于普通 DataContractSerializer 可见。

而且我必须使用 DataContractSerializer 而没有别的。

代码是 Silverlight 应用程序。

有人成功地完成了 DataContractSerializer 的子类化吗?

【问题讨论】:

  • 使用序列化代理通常是可行的方法——但它似乎在 silverlight 上不可用,请参阅stackoverflow.com/a/2750121/3744182
  • 在没有数据契约代理的情况下,您可能会考虑另一种方法:使用 this answer 中的 ElementSkippingXmlTextWriter 并在编写不需要的元素时跳过它们。
  • 唉,DataContractSerializer 是密封的,所以不能子类化。
  • @dbc 很有趣。谢谢!
  • 请将其作为答案发布

标签: c# .net silverlight serialization datacontractserializer


【解决方案1】:

您的问题的答案因以下问题而变得复杂:

  1. DataContractSerializer 是密封的,因此不能被子类化以检查像 MyIgnoreDataMember 这样的属性。

  2. 在序列化过程中使用serialization surrogate 将适当的DTO 注入对象图中通常是可行的方法——但它看起来在silverlight 上不可用,请参阅this answer

  3. DataContractSerializer 不支持ShouldSerialize 模式,正如here 所解释的那样,因此您不能只通过回调方法抑制不需要的属性的序列化。

那么,您有哪些选择?假设您的对象图如下所示:

[DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
public class RootObject
{
    [DataMember]
    public NestedObject NestedObject { get; set; }
}

[DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
public class NestedObject
{
    [DataMember]
    public string SensitiveData { get; set; }

    [DataMember]
    public string PublicData { get; set; }
}

并且您想有条件地抑制SensitiveData 的输出。那么有以下几种可能:

  1. 如果你只需要删除一些属性,你可以用EmitDefaultValue = false标记它们,并在某些线程静态为true时返回一个默认值,例如:

    [DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
    public class RootObject
    {
        [DataMember]
        public NestedObject NestedObject { get; set; }
    }
    
    [DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
    public class NestedObject
    {
        string sensitiveData;
    
        [DataMember(IsRequired = false, EmitDefaultValue = false)]
        public string SensitiveData
        {
            get
            {
                if (SerializationState.InCustomSerialization())
                    return null;
                return sensitiveData;
            }
            set
            {
                sensitiveData = value;
            }
        }
    
        [DataMember]
        public string PublicData { get; set; }
    }
    
    public static class SerializationState
    {
        [ThreadStatic]
        static bool inCustomSerialization;
    
        public static bool InCustomSerialization()
        {
            return inCustomSerialization;
        }
    
        public static IDisposable SetInCustomDeserialization(bool value)
        {
            return new PushValue<bool>(value, () => inCustomSerialization, b => inCustomSerialization = b);
        }
    }
    
    public struct PushValue<T> : IDisposable
    {
        Action<T> setValue;
        T oldValue;
    
        public PushValue(T value, Func<T> getValue, Action<T> setValue)
        {
            if (getValue == null || setValue == null)
                throw new ArgumentNullException();
            this.setValue = setValue;
            this.oldValue = getValue();
            setValue(value);
        }
    
        #region IDisposable Members
    
        // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
        public void Dispose()
        {
            if (setValue != null)
                setValue(oldValue);
        }
    
        #endregion
    }
    

    然后,在序列化时,执行以下操作:

    using (SerializationState.SetInCustomDeserialization(true))
    {
        // Serialize with data contract serializer.
    }
    

    真的很丑。

  2. 您可以使用与实际类型相同的合同名称和命名空间来创建整个 DTO 层次结构,使用 之类的东西将实际类映射到 DTO,然后序列化 DTO:

    [DataContract(Name = "Root", Namespace = "http://www.MyNamespace.com")]
    class RootObjectDTO
    {
        [DataMember]
        public NestedObjectDTO NestedObject { get; set; }
    }
    
    [DataContract(Name = "Nested", Namespace = "http://www.MyNamespace.com")]
    class NestedObjectDTO
    {
        [DataMember]
        public string PublicData { get; set; }
    }
    

    如果 automapper 在 silverlight 上不可用,您可以使用 DataContractSerializer 本身进行映射,因为合约名称和命名空间是相同的。 IE。 - 将真正的根对象序列化为 XML 字符串(或XDocument,如下所示),将中间 XML 反序列化为 DTO 根,然后序列化出 DTO。

  3. 您可以使用以下扩展类序列化到内存中的XDocumentis available in silverlight):

    public static partial class DataContractSerializerHelper
    {
        public static XDocument SerializeContractToXDocument<T>(this T obj)
        {
            return obj.SerializeContractToXDocument(null);
        }
    
        public static XDocument SerializeContractToXDocument<T>(this T obj, DataContractSerializer serializer)
        {
            var doc = new XDocument();
            using (var writer = doc.CreateWriter())
            {
                (serializer ?? new DataContractSerializer(obj.GetType())).WriteObject(writer, obj);
            }
            return doc;
        }
    
        public static T DeserializeContract<T>(this XDocument doc)
        {
            return doc.DeserializeContract<T>(null);
        }
    
        public static T DeserializeContract<T>(this XDocument doc, DataContractSerializer serializer)
        {
            if (doc == null)
                throw new ArgumentNullException();
            using (var reader = doc.CreateReader())
            {
                return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(reader);
            }
        }
    }
    

    接下来,使用XPATH queries 修剪不需要的元素,然后将XDocument 序列化为最终的XML 表示。

  4. 最后,如果性能和内存使用非常重要,您可以使用 this answer 中的 ElementSkippingXmlTextWriter 在编写时删除不需要的元素。

【讨论】:

    猜你喜欢
    • 2010-12-30
    • 2011-03-10
    • 1970-01-01
    • 2020-11-04
    • 2019-01-25
    • 2012-03-30
    • 2012-06-28
    相关资源
    最近更新 更多