Skip to content

Example Scenarios

Mike-EEE edited this page Nov 30, 2019 · 20 revisions

Configuration Profiles

The ExtendedXmlSerializer API allows for you to create pre-configured configuration containers, known as configuration profiles. This is useful for encapsulating re-used configurations so that you can use them across different projects, or if you have different ways you would like to configure your container and would like to easily switch between them during development.

You define your profiles and then create a configuration container through the use of the ConfiguredContainer static class to create a ConfigurationContainer that is configured via the provided configuration profile.

Let's first start with a simple configuration profile:

	sealed class Subject
	{
		public int Number { get; set; }

		public string Message { get; set; }
	}

	sealed class SimpleProfile : IConfigurationProfile
	{
		public static SimpleProfile Default { get; } = new SimpleProfile();

		SimpleProfile() {}

		public IConfigurationContainer Get(IConfigurationContainer parameter) 
			=> parameter.Type<Subject>()
		                .Member(x => x.Message)
		                .Name("NewMessage");
	}

Now let's create a configuration container with the profile. The configuration profile will apply its encapsulated configurations to the container once it has been created. We do this through the use of a ConfiguredProfile.New call:

	IConfigurationContainer container = ConfiguredContainer.New<SimpleProfile>();

	IExtendedXmlSerializer serializer = container.UseAutoFormatting()
	                          .EnableImplicitTyping(typeof(Subject))
	                          .UseOptimizedNamespaces()
	                          .Create();
	var instance = new Subject {Message = "Hello World!"};
	string document = serializer.Serialize(instance);

And the result of the document variable above is:

<?xml version="1.0" encoding="utf-8"?><Issue282Tests_Profiles-Subject Number="0" NewMessage="Hello World!" />

You can also combine configuration profiles:

	sealed class ComplexProfile : CompositeConfigurationProfile
	{
		public static ComplexProfile Default { get; } = new ComplexProfile();

		ComplexProfile() : base(SimpleProfile.Default, NumberProfile.Default) {}
	}

	sealed class NumberProfile : IConfigurationProfile
	{
		public static NumberProfile Default { get; } = new NumberProfile();

		NumberProfile() {}

		public IConfigurationContainer Get(IConfigurationContainer parameter) 
			=> parameter.Type<Subject>()
                        .Member(x => x.Number)
                        .Name("NewNumber");
	}

Create:

	IConfigurationContainer container = ConfiguredContainer.New<ComplexProfile>();

	IExtendedXmlSerializer serializer = container.UseAutoFormatting()
	                          .EnableImplicitTyping(typeof(Subject))
	                          .UseOptimizedNamespaces()
	                          .Create();
	var instance = new Subject {Message = "Hello World!", Number = 123};
	string document = serializer.Serialize(instance);

Result:

<?xml version="1.0" encoding="utf-8"?><Issue282Tests_Profiles-Subject Number="123" NewMessage="Hello World!" />

The above code is verified and can be reviewed in a passing test within our test suite here.

Monitor Serialization Pipeline

One scenario that you might want to engage in is to monitor events during the serialization (and deserialization) process. You might want to do this for logging or storage purposes.

The ISerializationMonitor [link] has the following callbacks:

  • OnSerializing
  • OnSerialized
  • OnDeserializing
  • OnActivating
  • OnActivated
  • OnDeserialized

Let's run through a very simple example of this scenario by storing a list of strings whenever they are encountered during a serialization via the OnSerialized callback.

The code written here can be seen in a passing test defined in our test suite here.

First, define the monitor:

	sealed class Monitor : ISerializationMonitor<string>
	{
		readonly List<string> _store;

		public Monitor(List<string> store) => _store = store;

		public void OnSerializing(IFormatWriter writer, string instance) {}

		public void OnSerialized(IFormatWriter writer, string instance)
		{
			_store.Add(instance);
		}

		public void OnDeserializing(IFormatReader reader, Type instanceType) {}

		public void OnActivating(IFormatReader reader, Type instanceType) {}

		public void OnActivated(string instance) {}

		public void OnDeserialized(IFormatReader reader, string instance) {}
	}

And our subject:

	sealed class Subject
	{
		public string Message { get; set; }
	}

And finally our serializer instance:

	var instances = new List<string>();
	IExtendedXmlSerializer serializer = new ConfigurationContainer().Type<string>()
	                                                                .WithMonitor(new Monitor(instances))
	                                                                .Create();

Now let's serialize one of our subject's with a message:

	const string message = "Hello World!";
	string document = serializer.Serialize(new Subject {Message = message});

When we poll the instances variable above now, we see that it contains the Hello World! message.

For a full working test demonstrating this scenario, see the test described here.

Create an Extension

Creating an extension is at the heart of ExtendedXmlSerializer's extension model.

In this very simple example, we are going to extend the configuration container that creates our root serializer to create a content serializer so that it always add the number 42 whenever it encounters number during both serialization and deserialization.

Here's our subject:

	sealed class Subject
	{
		public int Number { get; set; }
	}

If we create an instance of this subject with its Number set to 10, it will serialize that value with 52 and then deserialize with a

Now, why on earth would we ever want to do this?! For demonstration purposes, of course! 😁

All of the code provided in this article can be found in the form of a passing test as defined here.

Let's begin by defining our extension:

    sealed class Extension : ISerializerExtension
	{
		public static Extension Default { get; } = new Extension();

		Extension() {}

		public IServiceRepository Get(IServiceRepository parameter) => parameter.DecorateContentsWith<Contents>()
		                                                                        .Then();

		void ICommand<IServices>.Execute(IServices parameter) {}

		sealed class Contents : IContents
		{
			readonly IContents        _previous;
			readonly ISerializer<int> _number;

			public Contents(IContents previous)
				: this(previous, new AnswerToEverythingSerializer(previous.Get(typeof(int)).For<int>())) {}

			public Contents(IContents previous, ISerializer<int> number)
			{
				_previous = previous;
				_number   = number;
			}

			public ISerializer Get(TypeInfo parameter)
				=> parameter == typeof(int) ? _number.Adapt() : _previous.Get(parameter);
		}

		sealed class AnswerToEverythingSerializer : ISerializer<int>
		{
			readonly ISerializer<int> _previous;

			public AnswerToEverythingSerializer(ISerializer<int> previous) => _previous = previous;

			public int Get(IFormatReader parameter) => _previous.Get(parameter) + 42;

			public void Write(IFormatWriter writer, int instance)
			{
				_previous.Write(writer, instance + 42);
			}
		}
	}

Here this extension is used to register the AnswerToEverythingSerializer whenever a content serializer is requested for the type of int. The AnswerToEverythingSerializer adds 42 to a number when it serializes and then 42 again to a value when it deserializes it.

Now let's create a container that is extended with this extension:

	IExtendedXmlSerializer serializer = new ConfigurationContainer().EnableImplicitTyping(typeof(Subject))
	                                                                .Extend(Extension.Default)
	                                                                .Create();
	string document = serializer.Serialize(new Subject(){Number = 10});

When we inspect the document variable above, we get the following XML:

<?xml version=""1.0"" encoding=""utf-8""?>
<Subject><Number>52</Number></Subject>

Now let's deserialize it:

var number = serializer.Deserialize<Subject>(document).Number;

Inspecting the number variable reveals 94 as it added 42 to the serialized value of 52.

This is a very contrived example but hopefully it communicates the power of the extension model that is available in ExtendedXmlSerializer. For a working example of the code above, check out the passing test that is in our test suite that demonstrates the above scenario.

Register Custom Serializer

You have full control over the serialization of a particular type. Consider the following class structure:

	public class Subject
	{
		public Subject(string text, int number)
		{
			Text   = text;
			Number = number;
		}

		public string Text { get; }
		public int Number { get; }
	}

Now, while it is possible to use EnableParameterizedContent [feature, API] to serialize and deserialize the above contract, what we want to demonstrate here is the ability to have full control over how the above is stored and retrieved.

The following code can be found in a passing test stored here.

First, let's create a serializer:

	sealed class SubjectSerializer : ISerializer<Subject>
	{
		public static SubjectSerializer Default { get; } = new SubjectSerializer();

		SubjectSerializer() {}

		public Subject Get(IFormatReader parameter)
		{
			var parts  = parameter.Content().Split('|');
			var result = new Subject(parts[0], int.Parse(parts[1]));
			return result;
		}

		public void Write(IFormatWriter writer, Subject instance)
		{
			writer.Content($"{instance.Text}|{instance.Number}");
		}
	}

The above is very rudimentary and extremely error-prone (particularly the int.Parse call), but again what we are demonstrating here is full control over the serialization and deserialization process for this particular type. If you feel so inclined (and maybe a little crazy 😅), you could even modify the serializer to send and retrieve data from a database.

That is the point (and power!) of this scenario, as well as demonstrating how to register a serializer for a particular type.

Now that we have the serializer created, let's register it and create a root container:

	IExtendedXmlSerializer serializer = new ConfigurationContainer().Type<Subject>()
	                                                                .Register()
	                                                                .Serializer()
	                                                                .Using(SubjectSerializer.Default)
	                                                                .Create();
    Subject instance = new Subject("Hello World!", 123);
	string document = serializer.Serialize(instance);

The above will create the following XML:

<?xml version="1.0" encoding="utf-8"?>
<Subject xmlns="clr-namespace:Namespace;assembly=Assembly">Hello World!|123</Subject>

The above code can be viewed as a passing test here.

Serialization of Dictionary

You can serialize generic dictionary, that can store any type.

    public class TestClass
    {
        public Dictionary<int, string> Dictionary { get; set; }
    }
    TestClass obj = new TestClass
    {
        Dictionary = new Dictionary<int, string>
        {
            {1, "First"},
            {2, "Second"},
            {3, "Other"},
        }
    };

Output XML will look like:

    <?xml version="1.0" encoding="utf-8"?>
    <TestClass xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Dictianary;assembly=ExtendedXmlSerializer.Samples">
      <Dictionary>
        <Item xmlns="https://extendedxmlserializer.github.io/system">
          <Key>1</Key>
          <Value>First</Value>
        </Item>
        <Item xmlns="https://extendedxmlserializer.github.io/system">
          <Key>2</Key>
          <Value>Second</Value>
        </Item>
        <Item xmlns="https://extendedxmlserializer.github.io/system">
          <Key>3</Key>
          <Value>Other</Value>
        </Item>
      </Dictionary>
    </TestClass>

If you use UseOptimizedNamespaces function xml will look like:

    <?xml version="1.0" encoding="utf-8"?>
    <TestClass xmlns:sys="https://extendedxmlserializer.github.io/system" xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Dictianary;assembly=ExtendedXmlSerializer.Samples">
      <Dictionary>
        <sys:Item>
          <Key>1</Key>
          <Value>First</Value>
        </sys:Item>
        <sys:Item>
          <Key>2</Key>
          <Value>Second</Value>
        </sys:Item>
        <sys:Item>
          <Key>3</Key>
          <Value>Other</Value>
        </sys:Item>
      </Dictionary>
    </TestClass>

Migrate XML Based on Older Class Model

In standard XMLSerializer you can't deserialize XML in case you change model. In ExtendedXMLSerializer you can create migrator for each class separately. E.g.: If you have big class, that uses small class and this small class will be changed you can create migrator only for this small class. You don't have to modify whole big XML. Now I will show you a simple example.

If you had a class:

    public class TestClass
    {
        public int Id { get; set; }
        public string Type { get; set; }
    }

and generated XML look like:

    <?xml version="1.0" encoding="utf-8"?>
    <TestClass xmlns="clr-namespace:ExtendedXmlSerialization.Samples.MigrationMap;assembly=ExtendedXmlSerializer.Samples">
      <Id>1</Id>
      <Type>Type</Type>
    </TestClass>

Then you renamed property:

    public class TestClass
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

and generated XML look like:

    <?xml version="1.0" encoding="utf-8"?>
    <TestClass xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:version="1" xmlns="clr-namespace:ExtendedXmlSerialization.Samples.MigrationMap;assembly=ExtendedXmlSerializer.Samples">
      <Id>1</Id>
      <Name>Type</Name>
    </TestClass>

Then, you added new property and you wanted to calculate a new value during deserialization.

    public class TestClass
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }
    }

and new XML should look like:

    <?xml version="1.0" encoding="utf-8"?>
    <TestClass xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:version="2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.MigrationMap;assembly=ExtendedXmlSerializer.Samples">
      <Id>1</Id>
      <Name>Type</Name>
      <Value>Calculated</Value>
    </TestClass>

You can migrate (read) old version of XML using migrations:

    public class TestClassMigrations : IEnumerable<Action<XElement>>
    {
        public static void MigrationV0(XElement node)
        {
            XElement typeElement = node.Member("Type");
            // Add new node
            node.Add(new XElement("Name", typeElement.Value));
            // Remove old node
            typeElement.Remove();
        }
    
        public static void MigrationV1(XElement node)
        {
            // Add new node
            node.Add(new XElement("Value", "Calculated"));
        }
    
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    
        public IEnumerator<Action<XElement>> GetEnumerator()
        {
            yield return MigrationV0;
            yield return MigrationV1;
        }
    }

Then, you must register your TestClassMigrations class in configuration

    IExtendedXmlSerializer serializer = new ConfigurationContainer().ConfigureType<TestClass>()
                                                                    .AddMigration(new TestClassMigrations())
                                                                    .Create();