Custom DataContractSerializer

别来无恙 提交于 2019-12-25 08:38:52

问题


I want to serialize an object with DataMember attribute to be ignored on some properties.

Say I have custom attribute MyIgnoreDataMember.

I want properties marked with it to be invisible for my custom DataContractSerializer, but visible for the normal DataContractSerializer.

And I have to use DataContractSerializer and nothing else.

The code is a Silverlight app.

Anyone have done subclassing DataContractSerializer successfully?


回答1:


An answer to your question is complicated by the following issues:

  1. DataContractSerializer is sealed, so can't be subclassed to check for an attribute like MyIgnoreDataMember.

  2. Using a serialization surrogate to inject an appropriate DTO into your object graph during serialization would normally be the way to go -- but it looks like it's not available on silverlight, see this answer.

  3. DataContractSerializer does not support the ShouldSerialize pattern, as explained here, so you can't just suppress serialization of the undesired properties via a callback method.

So, what are your options? Let's say your object graph looks like the following:

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

And you want to conditionally suppress output of SensitiveData. Then the following are possibilities:

  1. If you only need to eliminate a few properties, you could mark them with EmitDefaultValue = false and return a default value when some thread static is true, for instance:

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

    And then, when serializing, do something like:

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

    Honestly quite ugly.

  2. You could make an entire DTO hierarchy with the same contract names and namespaces as your real types, map the real classes to the DTO with something like automapper, and serialize the DTOs:

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

    If automapper is not available on silverlight, you can use DataContractSerializer itself to do the mapping, since the contract names and namespaces are identical. I.e. - serialize the real root object to an XML string (or to an XDocument as shown below), deserialize the intermediate XML to the DTO root, then serialize out the DTO.

  3. You could serialize to an in-memory XDocument (which is available in silverlight) using the following extension class:

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

    Next, prune the undesired elements using XPATH queries, and then serialize the XDocument to a final XML representation.

  4. Finally, if performance and memory use are at a premium, you could use the ElementSkippingXmlTextWriter from this answer to prune undesired elements as they are written.



来源:https://stackoverflow.com/questions/42460674/custom-datacontractserializer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!