Skip to content

Features

Mike-EEE edited this page Nov 29, 2019 · 2 revisions

With the basics out of the way, we can turn our attention to what really makes ExtendedXmlSeralizer tick: its extensible featureset. As its name suggests, ExtendedXmlSeralizer offers a very flexible (but albeit new) extension model from which you can build your own extensions. Pretty much all if not all features you encounter with ExtendedXmlSeralizer are through extensions. There are quite a few in our latest version here that showcase this extensibility. The remainder of this document will showcase the top features of ExtendedXmlSerializer that are accomplished through its extension system.

Popular

Here are the features we've noticed users utilizing and mention when talking to us.

Optimized Namespaces

By default Xml namespaces are emitted on an "as needed" basis:

    <?xml version="1.0" encoding="utf-8"?>
    <List xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:arguments="Object" xmlns="https://extendedxmlserializer.github.io/system">
      <Capacity>4</Capacity>
      <Subject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
        <Message>First</Message>
      </Subject>
      <Subject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
        <Message>Second</Message>
      </Subject>
      <Subject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
        <Message>Third</Message>
      </Subject>
    </List>

But with one call to the UseOptimizedNamespaces call, namespaces get placed at the root of the document, thereby reducing document footprint:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().UseOptimizedNamespaces()
                                                                    .Create();
    List<object> subject = new List<object>
                    {
                        new Subject {Message = "First"},
                        new Subject {Message = "Second"},
                        new Subject {Message = "Third"}
                    };
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <List xmlns:ns1="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples" xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:arguments="Object" xmlns="https://extendedxmlserializer.github.io/system">
      <Capacity>4</Capacity>
      <ns1:Subject>
        <Message>First</Message>
      </ns1:Subject>
      <ns1:Subject>
        <Message>Second</Message>
      </ns1:Subject>
      <ns1:Subject>
        <Message>Third</Message>
      </ns1:Subject>
    </List>

Implicit Namespaces/Typing

If you don't like namespaces at all, you can register types so that they do not emit namespaces when they are rendered into a document:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableImplicitTyping(typeof(Subject))
                                                                    .Create();
    Subject subject = new Subject{ Message = "Hello World!  No namespaces, yay!" };
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <Subject>
      <Message>Hello World!  No namespaces, yay!</Message>
    </Subject>

Immutable Classes and Content

This is a favorite feature of ours in ExtendedXmlSerlializer. The classic serializer only supports parameterless public constructors. With ExtendedXmlSerializer, you can use the EnableParameterizedContent call to enable immutable classes. These are classes defined by parameterized content in the constructor that by convention have the same name as the property for which they are meant to assign:

    public sealed class ImmutableSubject
    {
        public ImmutableSubject(string message, int number, DateTime time)
        {
            Message = message;
            Number = number;
            Time = time;
        }
    
        public string Message { get; }
        public int Number { get; }
        public DateTime Time { get; }
    }
    IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableParameterizedContent()
                                                                    .Create();
    ImmutableSubject subject = new ImmutableSubject("Hello World!", 123, DateTime.Now);
    string contents = serializer.Serialize(subject);
    <?xml version="1.0" encoding="utf-8"?>
    <ImmutableSubject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
      <Message>Hello World!</Message>
      <Number>123</Number>
      <Time>2018-05-26T11:52:19.7551187-04:00</Time>
    </ImmutableSubject>

Note that we follow the same rules that Protobuf.net utilizes for their implementation of same feature:

The rules are:

  1. only public fields / properties are considered
  2. any public fields (spit) must be readonly
  3. any public properties must have a get but not a set (on the public API, at least)
  4. there must be exactly one interesting constructor, with parameters that are a case-insensitive match for each field/property in some order (i.e. there must be an obvious 1:1 mapping between members and constructor parameter names)

If you have settable properties on your class that are not defined in the constructor, you can use the EnableParameterizedContentWithPropertyAssignments method.

Notable

Other notable features of the ExtendedXmlSerializer consist of the following:

Object reference and circular reference

If you have a class:

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public Person Boss { get; set; }
    }
    
    public class Company
    {
        public List<Person> Employees { get; set; }
    }

then you create object with circular reference, like this:

    Person boss = new Person {Id = 1, Name = "John"};
    boss.Boss = boss; //himself boss
    Person worker = new Person {Id = 2, Name = "Oliver"};
    worker.Boss = boss;
    Company obj = new Company
    {
        Employees = new List<Person>
        {
            worker,
            boss
        }
    };

You must configure Person class as reference object:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().ConfigureType<Person>()
                                                                    .EnableReferences(p => p.Id)
                                                                    .Create();

Output XML will look like this:

    <?xml version="1.0" encoding="utf-8"?>
    <Company xmlns="clr-namespace:ExtendedXmlSerializer.Samples.ObjectReference;assembly=ExtendedXmlSerializer.Samples">
      <Employees>
        <Capacity>4</Capacity>
        <Person Id="2">
          <Name>Oliver</Name>
          <Boss Id="1">
            <Name>John</Name>
            <Boss xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:entity="1" />
          </Boss>
        </Person>
        <Person xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:entity="1" />
      </Employees>
    </Company>

How Circular References Are Handled

Have you ever used a serializer and discovered there are three "states" for circular references? Usually classified by an enumeration (the close cousin of booleanitis):

  • Ignore (wat?)
  • Handle
  • Throw

The first one is a head-scratcher. Since we are talking about a perpetual, endless loop, why would you want to enable this scenario without fail? Or,if you are going to ignore this property altogether, why provide a special case for a configuration that should be allowed for every member (ala IgnoreMemberAttributre or our Ignore call)?

Upon analysis there are only two states here: throw or collect the data. By default, ExtendedXmlSerializer throws as it is the most performant and also allows the user to know that something special is occurring in their object graph.

The use of EnableReferences allows them to collect their object graph and continue meaning serialization.

More information around this process can be found here: https://github.com/wojtpl2/ExtendedXmlSerializer/issues/202

Property Encryption

If you have a class with a property that needs to be encrypted:

    public class Person
    {
        public string Name { get; set; }
        public string Password { get; set; }
    }

You must implement interface IEncryption. For example, it will show the Base64 encoding, but in the real world better to use something safer, eg. RSA.:

    public class CustomEncryption : IEncryption
    {
        public string Parse(string data)
            => Encoding.UTF8.GetString(Convert.FromBase64String(data));
    
        public string Format(string instance)
            => Convert.ToBase64String(Encoding.UTF8.GetBytes(instance));
    }

Then, you have to specify which properties are to be encrypted and register your IEncryption implementation.

    IExtendedXmlSerializer serializer = new ConfigurationContainer().UseEncryptionAlgorithm(new CustomEncryption())
                                                                    .ConfigureType<Person>()
                                                                    .Member(p => p.Password)
                                                                    .Encrypt()
                                                                    .Create();

Custom Conversion

ExtendedXmlSerializer does a pretty decent job (if we do say so ourselves) of composing and decomposing objects, but if you happen to have a type that you want serialized in a certain way, and this type can be destructured into a string, then you can register a custom converter for it.

Using the following:

    public sealed class CustomStructConverter : IConverter<CustomStruct>
    {
        public static CustomStructConverter Default { get; } = new CustomStructConverter();
        CustomStructConverter() {}
    
        public bool IsSatisfiedBy(TypeInfo parameter) => typeof(CustomStruct).GetTypeInfo()
                                                                             .IsAssignableFrom(parameter);
    
        public CustomStruct Parse(string data) =>
            int.TryParse(data, out int number) ? new CustomStruct(number) : CustomStruct.Default;
    
        public string Format(CustomStruct instance) => instance.Number.ToString();
    }
    
    public struct CustomStruct
    {
        public static CustomStruct Default { get; } = new CustomStruct(6776);
    
        public CustomStruct(int number)
        {
            Number = number;
        }
        public int Number { get; }
    }

Register the converter:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().Register(CustomStructConverter.Default).Create();
    CustomStruct subject = new CustomStruct(123);
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <CustomStruct xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">123</CustomStruct>

Auto-Formatting (Attributes)

The default behavior for emitting data in an Xml document is to use elements, which can be a little chatty and verbose:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().UseOptimizedNamespaces()
                                                                    .Create();
    List<object> subject = new List<object>
                    {
                        new Subject {Message = "First"},
                        new Subject {Message = "Second"},
                        new Subject {Message = "Third"}
                    };
    string contents = serializer.Serialize(subject);
    <?xml version="1.0" encoding="utf-8"?>
    <SubjectWithThreeProperties xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
      <Number>123</Number>
      <Message>Hello World!</Message>
      <Time>2018-05-26T11:52:19.4981212-04:00</Time>
    </SubjectWithThreeProperties>

Making use of the UseAutoFormatting call will enable all types that have a registered IConverter (convert to string and back) to emit as attributes:

    <?xml version="1.0" encoding="utf-8"?>
    <SubjectWithThreeProperties Number="123" Message="Hello World!" Time="2018-05-26T11:52:19.4981212-04:00" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples" />

Verbatim Content (CDATA)

If you have an element with a member that can hold lots of data, or data that has illegal characters, you configure it to be a verbatim field and it will emit a CDATA section around it:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().Type<Subject>()
                                                                    .Member(x => x.Message)
                                                                    .Verbatim()
                                                                    .Create();
    Subject subject = new Subject {Message = @"<{""Ilegal characters and such""}>"};
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <Subject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
      <Message><![CDATA[<{"Ilegal characters and such"}>]]></Message>
    </Subject>

You can also denote these fields with an attribute and get the same functionality:

    public sealed class VerbatimSubject
    {
        [Verbatim]
        public string Message { get; set; }
    }

Private Constructors

One of the limitations of the classic XmlSerializer is that it does not support private constructors, but ExtendedXmlSerializer does via its EnableAllConstructors call:

    public sealed class SubjectByFactory
    {
        public static SubjectByFactory Create(string message) => new SubjectByFactory(message);
    
        SubjectByFactory() : this(null) {} // Used by serializer.
    
        SubjectByFactory(string message) => Message = message;
    
        public string Message { get; set; }
    }
    IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableAllConstructors()
                                                                    .Create();
    SubjectByFactory subject = SubjectByFactory.Create("Hello World!");
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <SubjectByFactory xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
      <Message>Hello World!</Message>
    </SubjectByFactory>

Tuples

By enabling parameterized content, it opens up a lot of possibilities, like being able to serialize Tuples. Of course, serializable Tuples were introduced recently with the latest version of C#. Here, however, you can couple this with our member-naming funtionality and provide better naming for your tuple properties:

    IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableParameterizedContent()
                                                                    .Type<Tuple<string>>()
                                                                    .Member(x => x.Item1)
                                                                    .Name("Message")
                                                                    .Create();
    Tuple<string> subject = Tuple.Create("Hello World!");
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <Tuple xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:arguments="string" xmlns="https://extendedxmlserializer.github.io/system">
      <Message>Hello World!</Message>
    </Tuple>

Experimental

These features are experimental, but please try them out and let us know what you think!

Xaml-ness: Attached Properties

We went ahead and got a little cute with v2 of ExtendedXmlSerializer, adding support for Attached Properties on objects in your serialized object graph. But instead of constraining it to objects that inherit from DependencyObject, every object can benefit from it. Check it out:

    sealed class NameProperty : ReferenceProperty<Subject, string>
    {
        public const string DefaultMessage = "The Name Has Not Been Set";
    
        public static NameProperty Default { get; } = new NameProperty();
        NameProperty() : base(() => Default, x => DefaultMessage) {}
    }
    
    sealed class NumberProperty : StructureProperty<Subject, int>
    {
        public const int DefaultValue = 123;
    
        public static NumberProperty Default { get; } = new NumberProperty();
        NumberProperty() : base(() => Default, x => DefaultValue) {}
    }
    IExtendedXmlSerializer serializer = new ConfigurationContainer()
                                        .EnableAttachedProperties(NameProperty.Default, NumberProperty.Default)
                                        .Create();
    Subject subject = new Subject {Message = "Hello World!"};
    subject.Set(NameProperty.Default, "Hello World from Attached Properties!");
    subject.Set(NumberProperty.Default, 123);
    
    string contents = serializer.Serialize(subject);
    // ...
    <?xml version="1.0" encoding="utf-8"?>
    <Subject xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples">
      <Message>Hello World!</Message>
      <NameProperty.Default>Hello World from Attached Properties!</NameProperty.Default>
      <NumberProperty.Default>123</NumberProperty.Default>
    </Subject>

Xaml-ness: Markup Extensions

Saving the most novel feature for last, we have experimental support for one of Xaml's greatest features, Markup Extensions:

    sealed class Extension : IMarkupExtension
    {
        const string Message = "Hello World from Markup Extension! Your message is: ", None = "N/A";
    
        readonly string _message;
    
        public Extension() : this(None) {}
    
        public Extension(string message)
        {
            _message = message;
        }
    
        public object ProvideValue(IServiceProvider serviceProvider) => string.Concat(Message, _message);
    }
    string contents =
        @"<?xml version=""1.0"" encoding=""utf-8""?>
            <Subject xmlns=""clr-namespace:ExtendedXmlSerializer.Samples.Extensibility;assembly=ExtendedXmlSerializer.Samples""
            Message=""{Extension 'PRETTY COOL HUH!!!'}"" />";
    IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableMarkupExtensions()
                                                                    .Create();
    Subject subject = serializer.Deserialize<Subject>(contents);
    Console.WriteLine(subject.Message); // "Hello World from Markup Extension! Your message is: PRETTY COOL HUH!!!"