How to save custom class in C# WPF? - c#

I'm developing a WPF application and I'm trying to do it without any reference to Windows Forms. I'm actually using the Wpf Font Picker by Alessio Saltarin which is a font picker with color picker completely developed in WPF.
What I'm trying to do is to let the user choose a font+color and save their choice in order to restore it for a new start of the application. The font is stored in the FontInfo class:
public class FontInfo
{
public SolidColorBrush BrushColor { get; set; }
public FontColor Color { get { return AvailableColors.GetFontColor(this.BrushColor); } }
public FontFamily Family { get; set; }
public double Size { get; set; }
public FontStretch Stretch { get; set; }
public FontStyle Style { get; set; }
public FontWeight Weight { get; set; }
public FamilyTypeface Typeface
{
get
{
FamilyTypeface ftf = new FamilyTypeface()
{
Stretch = this.Stretch,
Weight = this.Weight,
Style = this.Style
};
return ftf;
}
}
public FontInfo() { }
public FontInfo(FontFamily fam, double sz, FontStyle style, FontStretch strc, FontWeight weight, SolidColorBrush c)
{
this.Family = fam;
this.Size = sz;
this.Style = style;
this.Stretch = strc;
this.Weight = weight;
this.BrushColor = c;
}
public static void ApplyFont(Control control, FontInfo font)
{
control.FontFamily = font.Family;
control.FontSize = font.Size;
control.FontStyle = font.Style;
control.FontStretch = font.Stretch;
control.FontWeight = font.Weight;
control.Foreground = font.BrushColor;
}
public static FontInfo GetControlFont(Control control)
{
FontInfo font = new FontInfo()
{
Family = control.FontFamily,
Size = control.FontSize,
Style = control.FontStyle,
Stretch = control.FontStretch,
Weight = control.FontWeight,
BrushColor = (SolidColorBrush)control.Foreground
};
return font;
}
public static string TypefaceToString(FamilyTypeface ttf)
{
StringBuilder sb = new StringBuilder(ttf.Stretch.ToString());
sb.Append("-");
sb.Append(ttf.Weight.ToString());
sb.Append("-");
sb.Append(ttf.Style.ToString());
return sb.ToString();
}
}
I didn't get the point about what is the best option. I saw that it can be achieved by XML Serialization but I failed and I saw that every example is using simple classes with strings and int variables.
Cold you please point me to some documentation or give me an example on how to do this?

As types like SolidColorBrush are not decorated with the SerializableAttribute they don't serialize by default. Therefore you have do do it manually by implementing the ISerializable interface and converting those types into a serializable format e.g., string. This can be accomplished by using the build-in library converters that exist for almost all those types.
[Serializable]
public class FontInfo : ISerializable
{
public FontInfo()
{
// Empty constructor required to compile.
}
public SolidColorBrush BrushColor { get; set; }
//public FontColor Color { get { return AvailableColors.GetFontColor(this.BrushColor); } }
public FontFamily Family { get; set; }
public double Size { get; set; }
public FontStretch Stretch { get; set; }
public FontStyle Style { get; set; }
public FontWeight Weight { get; set; }
public FamilyTypeface Typeface =>
new FamilyTypeface()
{
Stretch = this.Stretch,
Weight = this.Weight,
Style = this.Style
};
// Implement this method to serialize data. The method is called
// on serialization e.g., by the BinaryFormatter.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Use the AddValue method to specify serialized values.
info.AddValue(nameof(this.BrushColor), new ColorConverter().ConvertToString(this.BrushColor.Color), typeof(string));
info.AddValue(nameof(this.Family), new FontFamilyConverter().ConvertToString(this.Family), typeof(string));
info.AddValue(nameof(this.Stretch), new FontStretchConverter().ConvertToString(this.Stretch), typeof(string));
info.AddValue(nameof(this.Style), new FontStyleConverter().ConvertToString(this.Style), typeof(string));
info.AddValue(nameof(this.Weight), new FontWeightConverter().ConvertToString(this.Weight), typeof(string));
info.AddValue(nameof(this.Size), this.Size, typeof(double));
}
// The special constructor is used
// e.g. by the BinaryFormatter to deserialize values.
public FontInfo(SerializationInfo info, StreamingContext context)
{
// Reset the property value using the GetValue method.
this.BrushColor = new SolidColorBrush((Color) ColorConverter.ConvertFromString((string)info.GetValue(nameof(this.BrushColor), typeof(string))));
this.Family = (FontFamily) new FontFamilyConverter().ConvertFromString((string)info.GetValue(nameof(this.Family), typeof(string)));
this.Stretch = (FontStretch) new FontStretchConverter().ConvertFromString((string)info.GetValue(nameof(this.Stretch), typeof(string)));
this.Style = (FontStyle) new FontStyleConverter().ConvertFromString((string)info.GetValue(nameof(this.Style), typeof(string)));
this.Weight = (FontWeight) new FontWeightConverter().ConvertFromString((string)info.GetValue(nameof(this.Weight), typeof(string)));
this.Size = (double) info.GetValue(nameof(this.Size), typeof(double));
}
}
Usage
public static void SerializeItem(string fileName)
{
var fontInfo = new FontInfo();
using (FileStream fileStream = File.Create(fileName))
{
var formatter = new BinaryFormatter();
formatter.Serialize(fileStream, fontInfo);
}
}
public static void DeserializeItem(string fileName)
{
using (FileStream fileStream = File.OpenRead(fileName))
{
var formatter = new BinaryFormatter();
FontInfo fontInfo = (FontInfo) formatter.Deserialize(fileStream);
}
}
Note
I don't know what FontColor is - must be a custom type of yours. In this case you need to decorate it with the SerializableAttribute too in order to serialize it (or provide a converter).
I didn't tested the code. Use this as a guide on how to serialize by implementing ISerializable.

You were on the right track with serialization, but it can be tricky at times. Here is a quick example you can try with your class that should work. This takes advantage of binary serialization.
First thing you must always do is make your class Serializable with an attribute like so.
[Serializable] //Add this attribute above your class
public class FontInfo
{
//...
}
Next try this simple example to see if your class serializes, saves to a file then deserializes.
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public void Example()
{
FontInfo fi = new FontInfo(){Size = 12};
BinaryFormatter bf = new BinaryFormatter();
// Serialize the Binary Object and save to file
using (FileStream fsout = new FileStream("FontInfo.txt", FileMode.Create, FileAccess.Write, FileShare.None))
{
bf.Serialize(fsout, fi);
}
//Open saved file and deserialize
using (FileStream fsin = new FileStream("FontInfo.txt", FileMode.Open, FileAccess.Read, FileShare.None))
{
FontInfo fi2 = (FontInfo)bf.Deserialize(fsin);
Console.WriteLine(fi2.Size); //Should output 12
}
}
This is just a quick example, but should get your started on the right track.

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.

Problems deserializing class after newly added member

I have a simple "serializable" class defined as follows:
[Serializable]
public class LayoutDetails
{
public bool IsRefreshEnabled { get; set; }
public string GridSettings { get; set; }
public List<string> GroupByPropertyNames { get; set; }
}
The class has been serialized using the following routine:
using (var memoryStream = new MemoryStream())
{
var dc = new DataContractSerializer(obj.GetType());
dc.WriteObject(memoryStream, obj);
memoryStream.Flush();
memoryStream.Position = 0;
StreamReader reader = new StreamReader(memoryStream);
serializedObject = reader.ReadToEnd();
}
I am trying to deserialize it using the following routine:
var bytes = new UTF8Encoding().GetBytes(serializedObject);
using (var stream = new MemoryStream(bytes ))
{
var serializer = new DataContractSerializer(type);
var reader = XmlDictionaryReader.CreateTextReader(stream, XmlDictionaryReaderQuotas.Max);
obj = serializer.ReadObject(reader, true);
}
This worked great; however, now, I want to add a new property to the class (public int? Offset { get; set; }). When I add this property to the class and try to deserialize previously serialized instances (without the property), I get an exception:
Error in line 1 position 148. 'EndElement' 'LayoutDetails' from namespace 'http://schemas.datacontract.org/2004/07/WpfApplication1' is not expected. Expecting element '_x003C_Offset_x003E_k__BackingField'.
My first thought was to implement ISerializable and use a deserialziation ctor, so I tried something like this, but it can't find any members by name - and I've tried several combinations...
public LayoutDetails(SerializationInfo info, StreamingContext context)
{
this.IsRefreshEnabled = (bool)info.GetValue("_x003C_IsRefreshEnabled_x003E_k__BackingField", typeof(bool));
this.IsRefreshEnabled = (bool)info.GetValue("_x003C_IsRefreshEnabled_x003E_k_", typeof(bool));
this.IsRefreshEnabled = (bool)info.GetValue("_x003C_IsRefreshEnabled_x003E", typeof(bool));
this.IsRefreshEnabled = (bool)info.GetValue("x003C_IsRefreshEnabled_x003E", typeof(bool));
this.IsRefreshEnabled = (bool)info.GetValue("IsRefreshEnabled", typeof(bool));
}
Using the DataContractSerializer, how can I add a new member to my class without breaking deserialization on "old" instances?
You can use backing fields for the new properties, and add attribute OptionalField:
[Serializable]
public class LayoutDetails
{
[OptionalField]
private int? offset;
public bool IsRefreshEnabled { get; set; }
public string GridSettings { get; set; }
public List<string> GroupByPropertyNames { get; set; }
public int? Offset
{
get { return offset; }
set { offset = value; }
}
}
if you want to setup a default value, you can add a method to the class with the attribute OnDeserializing:
[OnDeserializing]
private void SetOffsetDefault(StreamingContext sc)
{
offset = 123;
}

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

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.

Deserialize array values to .NET properties using DataContractJsonSerializer

I'm working with the DataContractJsonSerializer in Silverlight 4 and would like to deserialize the following JSON:
{
"collectionname":"Books",
"collectionitems": [
["12345-67890",201,
"Book One"],
["09876-54321",45,
"Book Two"]
]
}
Into classes like the following:
class BookCollection
{
public string collectionname { get; set; }
public List<Book> collectionitems { get; set; }
}
class Book
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
}
What's the proper place to extend DataContractJsonSerializer to map the unnamed first array element in "collectionitems" to the Id property of the Book class, the second element to the NumberOfPages property and the final element to Title? I don't have control over the JSON generation in this instance and would like the solution to work with the Silverlight subset of .NET. It would be great if the solution could perform the reverse for serialization as well.
If this weren't Silverlight, you could use IDataContractSurrogate to use object[] (what's actually present in your JSON) instead of Book when serializing/deserializing. Sadly, IDataContractSurrogate (and the overloads of the DataContractJsonSerializer constructor which use it) aren't available in Silverlight.
On Silverlight, here's a hacky but simple workaround. Derive the Book class from a type which imlpements ICollection<object>. Since the type in your serialized JSON is object[], the framework will dutifully serialize it into your ICollection<object>, which in turn you can wrap with your properties.
The easiest (and hackiest) is just to derive from List<object>. This easy hack has the downside that users can modify the underlying list data and mess up your properties. If you're the only user of this code, that might be OK. With a little more work, you can roll your own implementation of ICollection and permit only enough methods to run for serialization to work, and throwing exceptions for the rest. I included code samples for both approaches below.
If the above hacks are too ugly for you, I'm sure there are more graceful ways to handle this. You'd probably want to focus your attention on creating a custom collection type instead of List<Book> for your collectionitems property. This type could contain a field of type List<object[]> (which is the actual type in your JSON) which you might be able to convince the serializer to populate. Then your IList implementation could mine that data into actual Book instances.
Another line of investigation could try casting.For example could you implement an implicit type conversion between Book and string[] and would serialization be smart enough to use it? I doubt it, but it may be worth a try.
Anyway, here's code samples for the derive-from-ICollection hacks noted above. Caveat: I haven't verified these on Silverlight, but they should be using only Silverlight-accessible types so I think (fingers crossed!) it should work OK.
Easy, Hackier Sample
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
[CollectionDataContract]
class Book : List<object>
{
public string Id { get { return (string)this[0]; } set { this[0] = value; } }
public int NumberOfPages { get { return (int)this[1]; } set { this[1] = value; } }
public string Title { get { return (string)this[2]; } set { this[2] = value; } }
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
Harder, slightly-less-hacky sample
Here's the second sample, showing a manual implementation of ICollection, which prevents users from accessing the collection-- it supports calling Add() 3 times (during deserialization) but otherwise doesn't allow modification via ICollection<T>. The ICollection methods are exposed using explicit interface implementation and there are attributes on those methods to hide them from intellisense, which should further reduce the hack factor. But as you can see this is a lot more code.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
[CollectionDataContract]
class Book : ICollection<object>
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
// code below here is only used for serialization/deserialization
// keeps track of how many properties have been initialized
[EditorBrowsable(EditorBrowsableState.Never)]
private int counter = 0;
[EditorBrowsable(EditorBrowsableState.Never)]
public void Add(object item)
{
switch (++counter)
{
case 1:
Id = (string)item;
break;
case 2:
NumberOfPages = (int)item;
break;
case 3:
Title = (string)item;
break;
default:
throw new NotSupportedException();
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerator<object> System.Collections.Generic.IEnumerable<object>.GetEnumerator()
{
return new List<object> { Id, NumberOfPages, Title }.GetEnumerator();
}
[EditorBrowsable(EditorBrowsableState.Never)]
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new object[] { Id, NumberOfPages, Title }.GetEnumerator();
}
[EditorBrowsable(EditorBrowsableState.Never)]
int System.Collections.Generic.ICollection<object>.Count
{
get { return 3; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.IsReadOnly
{ get { throw new NotSupportedException(); } }
[EditorBrowsable(EditorBrowsableState.Never)]
void System.Collections.Generic.ICollection<object>.Clear()
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.Contains(object item)
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
void System.Collections.Generic.ICollection<object>.CopyTo(object[] array, int arrayIndex)
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.Remove(object item)
{ throw new NotSupportedException(); }
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
BTW, the first time I read your quesiton I skipped over the important Silverlight requirement. Oops! Anyway, if not using Silverlight, here's the solution I coded up for that case-- it's much easier and I might as well save it here for any Googlers coming later.
The (on regular .NET framework, not Silverlight) magic you're looking for is IDataContractSurrogate. Implement this interface when you want to substitute one type for another type when serializing/deserializing. In your case you wnat to substitute object[] for Book.
Here's some code showing how this works:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Collections.ObjectModel;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
class Book
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
}
// A type surrogate substitutes object[] for Book when serializing/deserializing.
class BookTypeSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
// "Book" will be serialized as an object array
// This method is called during serialization, deserialization, and schema export.
if (typeof(Book).IsAssignableFrom(type))
{
return typeof(object[]);
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
// This method is called on serialization.
if (obj is Book)
{
Book book = (Book) obj;
return new object[] { book.Id, book.NumberOfPages, book.Title };
}
return obj;
}
public object GetDeserializedObject(object obj, Type targetType)
{
// This method is called on deserialization.
if (obj is object[])
{
object[] arr = (object[])obj;
Book book = new Book { Id = (string)arr[0], NumberOfPages = (int)arr[1], Title = (string)arr[2] };
return book;
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null; // not used
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration; // Not used
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null; // not used
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null; // not used
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
return; // not used
}
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser =
new DataContractJsonSerializer(
typeof(BookCollection),
new List<Type>(), /* knownTypes */
int.MaxValue, /* maxItemsInObjectGraph */
false, /* ignoreExtensionDataObject */
new BookTypeSurrogate(), /* dataContractSurrogate */
false /* alwaysEmitTypeInformation */
);
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
I find your question very interesting. So I have to spent my time in trying to solve the problem. Currently I received an example which can serialize and deserealize JSON data like following:
{
"collectionname":"Books",
"collectionitems":[
{"book":["12345-67890",201,"Book One"]},
{"book":["09876-54321",45,"Book Two"]}
]
}
the corresponding code of a small console application:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace DataContractJsonSerializer {
[DataContract]
class BookCollection {
[DataMember (Order = 0)]
public string collectionname { get; set; }
[DataMember (Order = 1)]
public List<Book> collectionitems { get; set; }
}
[Serializable]
[KnownType (typeof (object[]))]
class Book: ISerializable {
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
public Book () { }
[SecurityPermissionAttribute (SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
protected Book (SerializationInfo info, StreamingContext context) {
// called by DataContractJsonSerializer.ReadObject
Object[] ar = (Object[]) info.GetValue ("book", typeof (object[]));
this.Id = (string)ar[0];
this.NumberOfPages = (int)ar[1];
this.Title = (string)ar[2];
}
[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
public void GetObjectData (SerializationInfo info, StreamingContext context) {
// called by DataContractJsonSerializer.WriteObject
object[] ar = new object[] { (object)this.Id, (object)this.NumberOfPages, (object)this.Title };
info.AddValue ("book", ar);
}
}
class Program {
static readonly string testJSONdata = "{\"collectionname\":\"Books\",\"collectionitems\":[{\"book\":[\"12345-67890\",201,\"Book One\"]},{\"book\":[\"09876-54321\",45,\"Book Two\"]}]}";
static void Main (string[] args) {
BookCollection test = new BookCollection () {
collectionname = "Books",
collectionitems = new List<Book> {
new Book() { Id = "12345-67890", NumberOfPages = 201, Title = "Book One"},
new Book() { Id = "09876-54321", NumberOfPages = 45, Title = "Book Two"},
}
};
MemoryStream memoryStream = new MemoryStream ();
System.Runtime.Serialization.Json.DataContractJsonSerializer ser =
new System.Runtime.Serialization.Json.DataContractJsonSerializer (typeof (BookCollection));
memoryStream.Position = 0;
ser.WriteObject (memoryStream, test);
memoryStream.Flush();
memoryStream.Position = 0;
StreamReader sr = new StreamReader(memoryStream);
string str = sr.ReadToEnd ();
Console.WriteLine ("The result of custom serialization:");
Console.WriteLine (str);
if (String.Compare (testJSONdata, str, StringComparison.Ordinal) != 0) {
Console.WriteLine ("Error in serialization: unexpected results.");
return;
}
byte[] jsonDataAsBytes = System.Text.Encoding.GetEncoding ("iso-8859-1").GetBytes (testJSONdata);
MemoryStream stream = new MemoryStream (jsonDataAsBytes);
stream.Position = 0;
BookCollection p2 = (BookCollection)ser.ReadObject (stream);
}
}
}
I don't yet tested this approach under Silverlight 4.

DataContractSerializer with Multiple Namespaces

I am using a DataContractSerializer to serialize an object to XML. The main object is SecurityHolding with the namespace "http://personaltrading.test.com/" and contains a property called Amount that's a class with the namespace "http://core.test.com". When I serialize this to XML I get the following:
<ArrayOfSecurityHolding xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<Amount xmlns:d3p1="http://core.test.com/">
<d3p1:Amount>1.05</d3p1:Amount>
<d3p1:CurrencyCode>USD</d3p1:CurrencyCode>
</Amount>
<BrokerageID>0</BrokerageID>
<BrokerageName i:nil="true" />
<RecordID>3681</RecordID>
</SecurityHolding></ArrayOfSecurityHolding>
Is there anyway I can control the d3p1 prefix? Am I doing something wrong or should I be doing something else?
Firstly, the choice of namespace alias should make no difference to a well-formed parser.
But; does it have to be DataContractSerializer? With XmlSerializer, you can use the overload of Serialize that accepts a XmlSerializerNamespaces. This allows you to pick and choose the namespaces and aliases that you use.
Ultimately; DataContractSerializer is not intended to give full xml control; that isn't its aim. If you want strict xml control, XmlSerializer is a better choice, even if it is older (and has some nuances/foibles of its own).
Full example:
using System;
using System.Xml.Serialization;
public class Amount
{
public const string CoreNamespace = "http://core.test.com/";
[XmlElement("Amount", Namespace=CoreNamespace)]
public decimal Value { get; set; }
[XmlElement("CurrencyCode", Namespace = CoreNamespace)]
public string Currency { get; set; }
}
[XmlType("SecurityHolding", Namespace = SecurityHolding.TradingNamespace)]
public class SecurityHolding
{
public const string TradingNamespace = "http://personaltrading.test.com/";
[XmlElement("Amount", Namespace = Amount.CoreNamespace)]
public Amount Amount { get; set; }
public int BrokerageId { get; set; }
public string BrokerageName { get; set; }
public int RecordId { get; set; }
}
static class Program
{
static void Main()
{
var data = new[] {
new SecurityHolding {
Amount = new Amount {
Value = 1.05M,
Currency = "USD"
},
BrokerageId = 0,
BrokerageName = null,
RecordId = 3681
}
};
var ser = new XmlSerializer(data.GetType(),
new XmlRootAttribute("ArrayOfSecurityHolding") { Namespace = SecurityHolding.TradingNamespace});
var ns = new XmlSerializerNamespaces();
ns.Add("foo", Amount.CoreNamespace);
ser.Serialize(Console.Out, data, ns);
}
}
Output:
<ArrayOfSecurityHolding xmlns:foo="http://core.test.com/" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<foo:Amount>
<foo:Amount>1.05</foo:Amount>
<foo:CurrencyCode>USD</foo:CurrencyCode>
</foo:Amount>
<BrokerageId>0</BrokerageId>
<RecordId>3681</RecordId>
</SecurityHolding>
</ArrayOfSecurityHolding>
I have solved this problem slightly differently to Marc that can be implemented in a base class.
Create a new attribute to define the additional XML namespaces that you will use in your data contract.
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public sealed class NamespaceAttribute : Attribute
{
public NamespaceAttribute()
{
}
public NamespaceAttribute(string prefix, string uri)
{
Prefix = prefix;
Uri = uri;
}
public string Prefix { get; set; }
public string Uri { get; set; }
}
Add the attribute to your data contracts.
[DataContract(Name = "SomeObject", Namespace = "http://schemas.domain.com/namespace/")]
[Namespace(Prefix = "a", Uri = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
[Namespace(Prefix = "wm", Uri = "http://schemas.datacontract.org/2004/07/System.Windows.Media")]
public class SomeObject : SerializableObject
{
private IList<Color> colors;
[DataMember]
[DisplayName("Colors")]
public IList<Colors> Colors
{
get { return colors; }
set { colours = value; }
}
}
Then in your Save method, use reflection to get the attributes and then write them to the file.
public static void Save(SerializableObject o, string filename)
{
using (Stream outputStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
if (outputStream == null)
throw new ArgumentNullException("Must have valid output stream");
if (outputStream.CanWrite == false)
throw new ArgumentException("Cannot write to output stream");
object[] attributes;
attributes = o.GetType().GetCustomAttributes(typeof(NamespaceAttribute), true);
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Indent = true;
writerSettings.NewLineOnAttributes = true;
using (XmlWriter w = XmlWriter.Create(outputStream, writerSettings))
{
DataContractSerializer s = new DataContractSerializer(o.GetType());
s.WriteStartObject(w, o);
foreach (NamespaceAttribute ns in attributes)
w.WriteAttributeString("xmlns", ns.Prefix, null, ns.Uri);
// content
s.WriteObjectContent(w, o);
s.WriteEndObject(w);
}
}
}
I have struggled with this problem also. The solution I present below is not optimal IMHO but it works. Like Marc Gravell above, I suggest using XmlSerializer.
The trick is to add a field to your class that returns a XmlSerializerNamespaces object. This field must be decorated with a XmlNamespaceDeclarations attribute. In the constructor of your class, add namespaces as shown in the example below. In the xml below note that the root element is prefixed correctly as well as the someString element.
More info on XmlSerializerNamespaces
Schemas reference
[XmlRoot(Namespace="http://STPMonitor.myDomain.com")]
public class CFMessage : IQueueMessage<CFQueueItem>
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns;
[XmlAttribute("schemaLocation", Namespace=System.Xml.Schema.XmlSchema.InstanceNamespace)]
public string schemaLocation = "http://STPMonitor.myDomain.com/schemas/CFMessage.xsd";
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("username")]
public string UserName { get; set; }
[XmlAttribute("somestring", Namespace = "http://someURI.com")]
public string SomeString = "Hello World";
public List<CFQueueItem> QueueItems { get; set; }
public CFMessage()
{
xmlns = new XmlSerializerNamespaces();
xmlns.Add("myDomain", "http://STPMonitor.myDomain.com");
xmlns.Add("xyz", "http://someURI.com");
}
}
<?xml version="1.0" encoding="utf-16"?>
<myDomain:CFMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xyz="http://someURI.com"
xsi:schemaLocation="http://STPMonitor.myDomain.com/schemas/CFMessage.xsd"
xyz:somestring="Hello World" type="JOIN" username="SJ-3-3008-1"
xmlns:myDomain="http://STPMonitor.myDomain.com" />
Add "http://www.w3.org/2001/XMLSchema" namespace by:
private static string DataContractSerialize(object obj)
{
StringWriter sw = new StringWriter();
DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
using (XmlTextWriter xw = new XmlTextWriter(sw))
{
//serializer.WriteObject(xw, obj);
//
// Insert namespace for C# types
serializer.WriteStartObject(xw, obj);
xw.WriteAttributeString("xmlns", "x", null, "http://www.w3.org/2001/XMLSchema");
serializer.WriteObjectContent(xw, obj);
serializer.WriteEndObject(xw);
}
StringBuilder buffer = sw.GetStringBuilder();
return buffer.ToString();
}

Categories

Resources