XmlSerializer - Best way to migrate a xml file to a new version - c#

Let's assume I have a simple class
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string Image { get; set; } // Base64 coded Bitmap object
}
The real world object is way more complex. I use XmlSerializer.Serialize to save instances to a file.
The content from image is generated this way:
byte[] result = null;
using (var image = Bitmap.FromFile(#"filename"))
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
result = stream.ToArray();
}
var content = Convert.ToBase64String(result);
Now I have a breaking change.
In the future I want to save the raw image Data (also as base64) without converting it to jpg.
So my New object will look like this:
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
}
Luckily I already store a version attribute (currently 1 for every xml file). For new Items I can
Now I am wondering if there are any best practices on how to deal with model changed.
I was thinking about this approach:
Still define a property ImageString in my class, marked as obsolete.
the property will only have a setter but no getter
If ImageString is set I just update RawImageString
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
[Obsolete("Use RawImageString instead")]
public string ImageString
{
set
{
this.RawImageString = value;
this.Version = 2;
}
}
}
This should work well, but it would require me to maintain the legacy property until forever. I would prefer
// depending on the version property XmlSerializer should return a
// different Document implementation
var serializer = new XmlSerializer(typeof(IDocument));
var document = (IDocument)serializer.Deserialize(reader);
Of course I could achive this with a factory method, but that would require two reads. One for the version the second for the concrete result.

Eventually I solved it this way.
A Document is created using a static method anyway.
Now I check if version matches the current version and start a migration if not.
public const int CURRENT_VERSION = 2;
public static DocumentOpen(string path)
{
var controller = new DocumentController();
var item = controller.ReadXml(path);
if (item.Version != CURRENT_VERSION)
{
var migrator = new DocumentMigrator(item, path);
migrator.MigrateToLatestVersion();
}
return item;
}
The migrator looks like this
public class DocumentMigrator
{
private Document item;
private String path;
public DocumentMigrator(Documentitem, string path)
{
this.item = item;
this.path = path;
}
public void MigrateToLatestVersion()
{
Migrate(Document.CURRENT_VERSION);
}
public void Migrate(int to)
{
Migrate(item.Version, to);
}
private void Migrate(int from, int to)
{
if (from < to)
{
while (item.Version < to)
Up(item.Version + 1);
}
else if (from > to)
{
while (item.Version < to)
Down(item.Version - 1);
}
}
private void Down(int version)
{
throw new NotImplementedException();
}
private void Up(int version)
{
if (version == 2)
{
var stream = File.OpenRead(path);
var serializer = new XmlSerializer(typeof(DocumentV1));
var document = (DocumentV1)serializer.Deserialize(stream);
this.item.RawImageString = document.ImageString;
}
else
{
throw new NotImplementedException();
}
this.item.Version = version;
}
}
public class DocumentV1
{
public string ImageString { get; set; }
}
The idea is that I created a helper class DocumentV1 which only contains the properties that I want to migrate. This could even be avoided by using dynamics or XElement.
I perform the upgrade and update the version from the original class. A Backward migration could also be implemented in the Down method but that is not required at the moment.

Related

How to Serialize Object to Xml

The class I want to store:
[Serializable]
public class Storagee
{
int tabCount;
List<string> tabNames;
List<EachItemListHolder> eachItemsHolder;
public void PreSetting(int count, List<string> strings, List<EachItemListHolder> items)
{
tabCount = count;
tabNames = strings;
eachItemsHolder = items;
}
public void PreSetting(int count ) //debug purpose
{
tabCount = count;
}
public int GetTabCount() { return tabCount; }
public List<string> GetTabNames() { return tabNames; }
public List<EachItemListHolder> GetListEachItemListHolder() { return eachItemsHolder; }
}
Serializing class:
namespace Book
{
class SaveAndLoad
{
public void SaveAll(Storagee str)
{
var path = #"C:\Temp\myserializationtest.xml";
using (FileStream fs = new FileStream(path, FileMode.Create))
{
XmlSerializer xSer = new XmlSerializer(typeof(Storagee));
xSer.Serialize(fs, str);
}
}
public Storagee LoadAll()
{
var path = #"C:\Temp\myserializationtest.xml";
using (FileStream fs = new FileStream(path, FileMode.Open)) //double
{
XmlSerializer _xSer = new XmlSerializer(typeof(Storagee));
var myObject = _xSer.Deserialize(fs);
return (Storagee)myObject;
}
}
}
}
Main method (Window form):
class Book
{
List<EachTab> eachTabs;
Storagee storagee;
SaveAndLoad saveAndLoad;
eachTabs = new List<EachTab>();
storagee = new Storagee();
saveAndLoad = new SaveAndLoad();
void Saving()
{
int count = UserTab.TabCount; // tab counts
storagee.PreSetting(count);
saveAndLoad.SaveAll(storagee);
}
}
It makes xml file but doesn't save data.
I tried the serializing code in different project and it worked.
but it doesn't in this solution
since I'm kind of new to coding I don't know what the problem is
especially serializing part.
serializing codes are copied and pasted with little tweak
It makes xml file but doesn't save data.
It doesn't save any data because your class does not provide any data that it can serialize. XmlSerializer only serializes public fields and properties and the Storagee class doesn't have any.
You could, for example, change your public getter methods to public properties:
public int TabCount { get; set; }
public List<string> TabNames { get; set; }
public List<string> EachItemsHolder { get; set; }
Alternatively, if using public properties is not an option, you could also look into using custom serialization by implementing IXmlSerializable.

How can i store my List<List<Class>> to a text file using C# in WPF?

i'm trying to store a List<List<Class>> to a text file using C#, my class is like
public class cEspecie
{
private string nombre;
private BitmapImage imagen;
private int tiempodevida;
private int movilidad;
private int decaimiento;
private int tipo;
private string colordelagrafica;
private bool seleccion;
public int Tipo
{
get
{
return tipo;
}
set
{
tipo = value;
}
}
public string Nombre
{
get
{
return nombre;
}
set
{
nombre = value;
}
}
public BitmapImage Imagen
{
get
{
return imagen;
}
set
{
imagen = value;
}
}
public int TiempoDeVida
{
get
{
return tiempodevida;
}
set
{
tiempodevida = value;
}
}
public int Movilidad
{
get
{
return movilidad;
}
set
{
movilidad = value;
}
}
public int Decaimiento
{
get
{
return decaimiento;
}
set
{
decaimiento = value;
}
}
public string ColorDeLaGrafica
{
get
{
return colordelagrafica;
}
set
{
colordelagrafica = value;
}
}
public bool Seleccion
{
get
{
return seleccion;
}
set
{
seleccion = value;
}
}
}
List<List<cEspecie>> items1 = new List<List<cEspecie>>();
How can I take all my data from "items1" in order to store it inside a Text file using C#? and also how can I make it back in order to store it in another list?
Easiest way I can think of is to actually save the data using Xaml. It's a fitting solution since you're already using WPF.
Simple example:
List<List<cEspecie>> items1 = new List<List<cEspecie>>();
// ... add items...
XamlServices.Save(#"c:\path\to\file.xaml", items1);
And then to load:
var items2 = XamlServices.Load(#"c:\path\to\file.xaml");
Note that for this to work, you will need to change Imagen from a BitmapImage to its base class, BitmapSource. This will only work if the image was loaded with a URI or file path, and it will be saved using that URI/path. If you want to prevent the Imagen property from being saved in the Xaml, you can annotate the property with [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)].
Here's the Xaml file my test code created:
<List x:TypeArguments="List(w:cEspecie)"
Capacity="4"
xmlns="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:w="clr-namespace:WpfXUnit;assembly=WpfXUnit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<List x:TypeArguments="w:cEspecie"
Capacity="4">
<w:cEspecie ColorDeLaGrafica="zomg"
Decaimiento="42"
Imagen="file:///w:/dump/test.png"
Movilidad="76"
Nombre="'ello gov'na"
Seleccion="True"
TiempoDeVida="32"
Tipo="1" />
</List>
</List>
I would look into using XML or JSON to serialize/deserialize your data. That way you can easily store the data in a file, send it over a Web Service etc.
It would be simple using Json.NET, the only problem would be to store the Bitmap.
string json = JsonConvert.SerializeObject(items1);
Depending on how you decide to store the bitmap, Json.NET is not able to store it as JSON, unless you do some magic.
The easiest way would be to not serialize the Bitmap property and instead load the Image when you deserialize the string.
A simple solution is available in this stackoverflow post.
Edit:
This is an example based on the that stackoverflow post above, using your code.
[JsonObject(MemberSerialization.OptIn)]
public class Especie
{
private string m_imagePath;
[JsonProperty]
public Tipo { get; set; }
[JsonProperty]
public string Nombre { get; set; }
[JsonProperty]
public int TiempoDeVida { get; set; }
[JsonProperty]
public int Movilidad { get; set; }
[JsonProperty]
public int Decaimiento { get; set; }
[JsonProperty]
public string ColorDeLaGrafica { get; set; }
[JsonProperty]
public bool Seleccion { get; set; }
// not serialized because mode is opt-in
public Bitmap Imagen { get; private set; }
[JsonProperty]
public string ImagePath
{
get { return m_imagePath; }
set
{
m_imagePath = value;
Imagen = Bitmap.FromFile(m_imagePath);
}
}
}
If all you had was basic types all you would need to do is put a [Serializable] attribute on your class and you could use a XmlSeralizer to write it out to a file.
[Serializable]
public class cEspecie
{
//...Snip...
}
However for that to work all of the elements inside the class must also be marked [Serializable], unfortunately BitmapImage is not.
To work around this we mark the unseralizeable object to be skipped, we then provide a new property that can be seralized that can represent the image
[Serializable]
public class cEspecie
{
//...Snip...
[XmlIgnore] //Skip this object
public BitmapImage Imagen
{
get { return imagen; }
set { imagen = value; }
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] //This creates a secret hidden property that does not show up in intellisense
[XmlElement("Imagen")] //It uses the name of the real property for it's name
public BitmapImageSearalizer ImagenSerialized
{
get
{
return new BitmapImageSearalizer(imagen);
}
set
{
imagen = value.GetSearalizedImage();
}
}
}
[Serializable]
internal BitmapImageSearalizer
{
public BitmapImageSearalizer(BitmapImage sourceImage)
{
//...Snip...
}
public BitmapImage GetSearalizedImage()
{
//...Snip...
}
//...Snip...
}
I leave it to you to write BitmapImageSearalizer I have not worked with the class enough to write it.
To show you a more complete known working example of how to do it, here is a snippet from one of my projects where I did this exact trick with just a plain Bitmap object
/// <summary>
/// 1 bit per pixel bitmap to use as the image source.
/// </summary>
[XmlIgnore]
public Bitmap BitmapImage
{
get { return _Bitmap; }
set
{
if (value.PixelFormat != System.Drawing.Imaging.PixelFormat.Format1bppIndexed)
throw new FormatException("Bitmap Must be in PixelFormat.Format1bppIndexed");
_Bitmap = value;
}
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[XmlElement("Bitmap")]
public byte[] BitmapSerialized
{
get
{ // serialize
if (BitmapImage == null) return null;
using (MemoryStream ms = new MemoryStream())
{
BitmapImage.Save(ms, ImageFormat.Bmp);
return ms.ToArray();
}
}
set
{ // deserialize
if (value == null)
{
BitmapImage = null;
}
else
{
using (MemoryStream ms = new MemoryStream(value))
{
BitmapImage = new Bitmap(ms);
}
}
}
}

How to programmatically set template for html rendered with ServiceStack Markdown?

I'm using the Razor/Markdown engine from ServiceStack and am having a bit of difficulty applying my own custom Template/Layout to some rendered Markdown. The Markdown content renders perfectly, I just want to inject that into a Template/Layout file of my choice.
Currently I have this (which works perfectly) :
var rootPath = HttpContext.Current.Server.MapPath("~/");
var markdownPath = Path.Combile(rootPath, "NotFound.md");
var format = new MarkdownFormat();
var markdownContent = File.ReadAllText(markdownPath);
const string pageTitle = "Not Found";
var page = new MarkdownPage(format, rootPath, pageTitle, markdownContent);
format.AddPage(page);
var scopeArgs = new Dictionary<string, object>();
var html = format.RenderDynamicPageHtml(pageTitle, scopeArgs);
Now I have a layout file I wish to use located at "~/ErrorLayout.cshtml", however I have no idea how to inject it. At first I thought to set the Template variable on MarkdownPage to my Layout file's path, however that didn't work. I then tried to call format.AddLayout() and unfortunately that threw an exception.
Any help would be highly appreciated, feel free to ask for any further clarification from myself if I have not made what I am trying to do clear.
So I have solved the problem, however I am uncertain if what I have done is the correct method or not, however it works and no exceptions are thrown. Perhaps somebody with a bit more knowledge could correct me if I have done this incorrectly (so I will leave this question open for a couple days before accepting my answer).
I have created a new class which implements the IVirtualPathProvider interface, and named it PathProvider
I have also created a class which implements the IVirtualFile interface, and named it VirtualFile
I then set the VirtualPathProvider in my instance of MarkdownFormat to a new instance of PathProvider. I then set the Template variable on my instance of Markdownpage to a relative path to the cshtml layout/template file I want to make use of, and within the two classes I mentioned before, returned related content for this Template when requested to do so.
My code now looks like this (in case somebody else has the same problem as me) :
var rootPath = HttpContext.Current.Server.MapPath("~/");
if (contents == null)
{
var notFoundPath = Path.Combine(rootPath, "NotFound.md");
contents = File.ReadAllText(notFoundPath);
}
var format = new MarkdownFormat
{
VirtualPathProvider = new PathProvider()
};
const string pageTitle = "Not Found";
var page = new MarkdownPage(format, rootPath, pageTitle, contents)
{
Template = "~/_Layout.cshtml"
};
format.AddPage(page);
var view = new Dictionary<string, object>();
var html = format.RenderDynamicPageHtml(pageTitle, view);
My PathProvider class looks like this :
public class PathProvider : IVirtualPathProvider
{
public IVirtualDirectory RootDirectory { get; private set; }
public string VirtualPathSeparator { get; private set; }
public string RealPathSeparator { get; private set; }
public string CombineVirtualPath(string basePath, string relativePath)
{
throw new NotImplementedException();
}
public bool FileExists(string virtualPath)
{
throw new NotImplementedException();
}
public bool DirectoryExists(string virtualPath)
{
throw new NotImplementedException();
}
public IVirtualFile GetFile(string virtualPath)
{
return new VirtualFile(this, virtualPath);
}
public string GetFileHash(string virtualPath)
{
throw new NotImplementedException();
}
public string GetFileHash(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
public IVirtualDirectory GetDirectory(string virtualPath)
{
throw new NotImplementedException();
}
public IEnumerable<IVirtualFile> GetAllMatchingFiles(string globPattern, int maxDepth = 2147483647)
{
throw new NotImplementedException();
}
public bool IsSharedFile(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
public bool IsViewFile(IVirtualFile virtualFile)
{
throw new NotImplementedException();
}
}
and finally my VirtualFile class :
public class VirtualFile : IVirtualFile
{
public IVirtualDirectory Directory { get; private set; }
public string Name { get; private set; }
public string VirtualPath { get; private set; }
public string RealPath { get; private set; }
public bool IsDirectory { get; private set; }
public DateTime LastModified { get; private set; }
public IVirtualPathProvider VirtualPathProvider { get; private set; }
public string Extension { get; private set; }
public VirtualFile(IVirtualPathProvider virtualPathProvider, string filePath)
{
VirtualPathProvider = virtualPathProvider;
VirtualPath = filePath;
RealPath = HttpContext.Current.Server.MapPath(filePath);
}
public string GetFileHash()
{
throw new NotImplementedException();
}
public Stream OpenRead()
{
throw new NotImplementedException();
}
public StreamReader OpenText()
{
throw new NotImplementedException();
}
public string ReadAllText()
{
return File.ReadAllText(RealPath);
}
}

Derived Class Deserialization

I have a problem with deserialization with my logic simulation program.
Here are my element classes:
public class AndGateData : TwoInputGateData
{
}
public class TwoInputGateData : GateData
{
public TwoInputGateData()
{
Input2 = new InputData();
Input1 = new InputData();
}
public InputData Input1 { get; set; }
public InputData Input2 { get; set; }
}
public class GateData : ElementData
{
public GateData()
{
OutputData = new OutputData();
}
public OutputData OutputData { get; set; }
}
public class ElementData
{
public int Delay { get; set; }
public Guid Id { get; set; }
}
And here are classes responsible for sockets:
public class InputData : SocketData
{
}
public class SocketData
{
public Guid Id { get; set; }
public SignalData SignalData { get; set; }
}
SignalData is not important here. So, I won't write it (in order to keep this question clean) here unless somebody says it is necessary.
CircuitData is very important:
[XmlRoot("Circuit")]
public class CircuitData
{
[XmlElement(typeof(AndGateData))]
[XmlElement(typeof(OrGateData))]
public List<ElementData> elements = new List<ElementData>();
public List<WireData> wires = new List<WireData>();
public void AddElement(ElementData element)
{
elements.Add(element);
}
public void AddWire(WireData wire)
{
wires.Add(wire);
}
}
Wires are not important right now.
Now, I have written some Serialization:
public class CircuitDataWriter
{
public static void Write(object obj, string fileName)
{
var xmlFormat = new XmlSerializer(typeof(CircuitData));
using(Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None) )
{
xmlFormat.Serialize(fStream,obj);
}
Console.WriteLine("Circuit saved in XML format.");
}
}
It works just like I wanted, it produces that xml document:
<?xml version="1.0"?>
-<Circuit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-<AndGateData>
<Delay>10</Delay>
<Id>bfee6dd7-5946-4b7b-9d0b-15d5cf60e2bf</Id>
-<OutputData> <Id>00000000-0000-0000-0000-000000000000</Id> </OutputData>
-<Input1> <Id>7c767caf-79a9-4c94-9e39-5c38ec946d1a</Id> <SignalData xsi:type="SignalDataOn"/> </Input1>
-<Input2> <Id>d2cad8f8-8528-4db3-9534-9baadb6a2a14</Id> <SignalData xsi:type="SignalDataOff"/> </Input2>
</AndGateData>
<wires/>
</Circuit>
But I have problem with my DESERIALIZATION. Here is the code:
public static CircuitData Read()
{
var reader = new XmlSerializer(typeof(CircuitData));
StreamReader file = new StreamReader("Circuit.xml");
var returnCircuitData = new CircuitData();
returnCircuitData = (CircuitData) reader.Deserialize(file);
return returnCircuitData;
}
Now, it deserializes my Circuit.xml to object, but this object only contains Id and Delay, it does not contain Input1, Input2 or Output. So, it is treated like Element, not like AndGate. I tried to solve it out for a day but it seems that no one has that kind of problem.
I have a suggestion for you, make the Write method generic like this and create the serializer using objectToSerialize.GetType():
public static void Write<T>(T objectToSerialize, string fileName)
{
var xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
...
}
The XmlSerializer.Deserialize() method returns object, you can make your Read method generic like this:
public static T Read<T>(string fileName)
{
var serializer = new XmlSerializer(typeof(T));
using (StreamReader file = new StreamReader(fileName))
{
return (T)serializer.Deserialize(file);
}
}
Other than that you might want to read about:
XmlInclude that is used when you serialize derived classes.
XmlArray and XmlArrayItem that are used for controlling serialization of arrays

Problem with serializing a dictionary wrapper

I defined two classes. First one...
[Serializable]
public class LocalizationEntry
{
public LocalizationEntry()
{
this.CatalogName = string.Empty;
this.Identifier = string.Empty;
this.Translation = new Dictionary<string, string>();
this.TranslationsList = new List<Translation>();
}
public string CatalogName
{
get;
set;
}
public string Identifier
{
get;
set;
}
[XmlIgnore]
public Dictionary<string, string> Translation
{
get;
set;
}
[XmlArray(ElementName = "Translations")]
public List<Translation> TranslationsList
{
get
{
var list = new List<Translation>();
foreach (var item in this.Translation)
{
list.Add(new Translation(item.Key, item.Value));
}
return list;
}
set
{
foreach (var item in value)
{
this.Translation.Add(item.Language, item.Text);
}
}
}
}
...where public List<Translation> TranslationsList is a wrapper for non-serializable public Dictionary<string, string> Translation.
Pair of key and value is defined as follows:
[Serializable]
public class Translation
{
[XmlAttribute(AttributeName = "lang")]
public string Language
{
get;
set;
}
[XmlText]
public string Text
{
get;
set;
}
public Translation()
{
}
public Translation(string language, string translation)
{
this.Language = language;
this.Text = translation;
}
}
At last code used to serialize:
static void Main(string[] args)
{
LocalizationEntry entry = new LocalizationEntry()
{
CatalogName = "Catalog",
Identifier = "Id",
};
entry.Translation.Add("PL", "jabłko");
entry.Translation.Add("EN", "apple");
entry.Translation.Add("DE", "apfel");
using (FileStream stream = File.Open(#"C:\entry.xml", FileMode.Create))
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
serializer.Serialize(stream, entry);
}
LocalizationEntry deserializedEntry;
using (FileStream stream = File.Open(#"C:\entry.xml", FileMode.Open))
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
}
}
The problem is that after deserialization deserializedEntry.TranslationsList is empty. I set a breakpoint at setter of LocalizationEntry.TransalionsList and it comes from deserializer empty as well. Product of serialization is of course valid. Is there any gap in my code?
EDIT:
Here is generated XML:
<?xml version="1.0"?>
<LocalizationEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CatalogName>Catalog</CatalogName>
<Identifier>Id</Identifier>
<Translations>
<Translation lang="PL">jabłko</Translation>
<Translation lang="EN">apple</Translation>
<Translation lang="DE">apfel</Translation>
</Translations>
</LocalizationEntry>
The problem is that your TranslationList property is not being set by the Xml Deserializer. The set method will be hit but only by the call to this.TranslationsList = new List(); in the LocalisationEntry constructor. I'm not yet sure why but I suspect it's because it doesn't know how to convert an array of Translation objects back into a List.
I added the following code and it worked fine:
[XmlArray(ElementName = "Translations")]
public Translation[] TranslationArray
{
get
{
return TranslationsList.ToArray();
}
set
{
TranslationsList = new List<Translation>(value);
}
}
[XmlIgnore]
public List<Translation> TranslationsList
....
I am guessing the problem has to do with this:
public List<Translation> TranslationsList
The get/set operators are designed only for something to get or assign a fully-formed list. If you tried to use this in your own code, for example, every time you would do something like
TranslationsList.Add(item)
It would just create a new list from the existing dictionary and not actually deal with your item. I bet the deserializer works much the same way: uses set to create the new object once, then uses get as it adds each item from the XML. Since all that happens in get is it copies from the dictionary (which is empty when you begin your deserialization) you end up with nothing.
Try replacing this with just a field:
public List<Translation> TranslationsList;
and then explicitly call the code to copy the dictionary to this list before you serialize, and copy it from this list to the dictionary after you deserialize. Assuming that works, you can probably figure out a more seamless way to implement what you're trying to do.
I've created a sample, which will allow you to avoid the unnecessary hidden property when using the XmlSerializer:
class Program
{
static void Main(string[] args)
{
LocalizationEntry entry = new LocalizationEntry()
{
CatalogName = "Catalog",
Identifier = "Id",
Translations =
{
{ "PL", "jabłko" },
{ "EN", "apple" },
{ "DE", "apfel" }
}
};
using (MemoryStream stream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
serializer.Serialize(stream, entry);
stream.Seek(0, SeekOrigin.Begin);
LocalizationEntry deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
serializer.Serialize(Console.Out, deserializedEntry);
}
}
}
public class LocalizationEntry
{
public LocalizationEntry() { this.Translations = new TranslationCollection(); }
public string CatalogName { get; set; }
public string Identifier { get; set; }
[XmlArrayItem]
public TranslationCollection Translations { get; private set; }
}
public class TranslationCollection
: Collection<Translation>
{
public TranslationCollection(params Translation[] items)
{
if (null != items)
{
foreach (Translation item in items)
{
this.Add(item);
}
}
}
public void Add(string language, string text)
{
this.Add(new Translation
{
Language = language,
Text = text
});
}
}
public class Translation
{
[XmlAttribute(AttributeName = "lang")]
public string Language { get; set; }
[XmlText]
public string Text { get; set; }
}
There are some drawbacks when working with the XmlSerializer class itself. The .NET guidelines encourage you the not provide public-setters for collection-properties (like your translation list). But when you look at the code generated by the XmlSerializer, you'll see that it will use the Setter regardless of it is accessible. This results in a compile-error when the interim class is dynamically loaded by the XmlSerializer. The only way to avoid this, is to make the XmlSerializer think, that it can't actually create an instance of the list and thus won't try to call set for it. If the XmlSerializer detects that it can't create an instance it will throw an exception instead of using the Setter and the interim class is compiled successfully. I've used the param-keyword to trick the serializer into thinking that there is no default-constructor.
The only drawback from this solution is that you have to use a non-generic, non-interface type for the property (TranslationCollection) in my example.

Categories

Resources