I want to serialize IParsable<MyType> as <mns:MyType>MyType.ToString()</mns:MyType> in the context of a broader serialization requiring the DataContract serialization approach (readonly fields, etc.).
I have tried several ways, including ISerializable and ISerializationSurrogate, but they have all led to a lot of code that looks confusingly different to the DataContract attributes used throughout, and leaves me sure I'm using the wrong tools. It should be easy - MyType casts to and from string. What's the easiest way?
The steps for implementing a data contract serialization surrogate in .NET Core for some type
MyTypeare as follows:Create a surrogate type that can be serialized via the data contract serializer, and can be converted back and forth from the original type
MyType.create a surrogate provider type
ISerializationSurrogateProviderthat implements the following methods:GetSurrogateType(Type)- returns the surrogate type when passedtypeof(MyType).GetObjectToSerialize(Object, Type)- returns an instance of the surrogate when passed an instance ofMyType.GetDeserializedObject(Object, Type)- returns an instance ofMyTypewhen passed the surrogate.Before serializing or deserializing, construct your own
DataContractSerializerand callDataContractSerializerExtensions.SetSerializationSurrogateProvider(provider)to register an instance of the provider from step #2 with the serializer.Important notes
The method for using data contract surrogates in .NET Framework is completely different from .NET Core. If you are working in .NET Framework, ignore this answer and see Data Contract Surrogates.
In your
ToString()method, be sure to format all numeric and other values usingCultureInfo.InvariantCulture. If you don't, XML generated in one locale will be unreadable in another. If you need a localizedToString()for some reason, makeMyTypeimplementIFormattableand useToString(default, CultureInfo.InvariantCulture)when serializing. (Theformatstring can be ignored.)You mentioned attempting to use
ISerializablefor yourMyType. This technique stores data as element/value pairs in the XML serialization stream. Since your desired XML contains only text and no markup elements, this approach is probably not appropriate.With that in mind, we can create a generic surrogate for any type implementing
IParsable<TSelf>as follows:Next, make the following
ISerializationSurrogateProvider:Now, let's say your
MyTypelooks like this:Then to serialize from and to XML, you create a
DataContractSerializeras follows for any model type that contains an instance ofMyTypeas follows:Using the extension methods:
When
MyTypeis the root model itself, the XML generated will look like the following:Demo fiddle #1 here.
While this works, you will notice a couple problems:
The text value is nested inside a
<TextValue>element. In your question, you made it clear you don't want that.Since
DataContractSerializerdoes not support an equivalent to the[XmlText]attribute ofXmlSerializer, it will be necessary to implementIXmlSerializable.The root element name,
<ParsableSurrogateOfMyTypeMTRdQN6P>, is stupid, and doesn't match your requirement, which is<mns:MyType>.Since we will be implementing
IXmlSerializable, it will be necessary to use the solution from this answer to How can I control the root element namespace and name when serializing an IXmlSerializable object with the data contract serializer?.This requires modifying
ParsableSurrogate<TSelf>as follows:And adding the required data contract namespace to
MyTypelike so:Having done this, the following XML will be generated for
MyType:And for a model that contains a list of
MyTypesuch as the following:The following XML is generated, as required:
Demo fiddle #2 here.