问题
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:
DataContractSerializer is sealed, so can't be subclassed to check for an attribute like
MyIgnoreDataMember
.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.
DataContractSerializer
does not support theShouldSerialize
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:
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.
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 anXDocument
as shown below), deserialize the intermediate XML to the DTO root, then serialize out the DTO.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.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