C# XML Serialization - weakening encapsulation? - c#

Am I correct in thinking that, in order to get C# to serialize an object, I MUST have a public property for every field that needs its state stored?
If so, is that not very very sucky, as it weakens (if not breaks entirely) any encapsulation my class has?
In Java, XStream can iterate over every non-transient field and archive it. In C# this can't happen, and just to make things worse, things like Dictionaries don't serialize AT ALL. It's all a bit of a mess, no?
I've seen the DLL for a "port" of XStream to .net, but there are no docs and I'm suspicious.

You should use DataContractSerializer, and mark every property/field you want to serialize with [DataMember]. It doesn't care if your fields are private or public. By the way you can serialize Dictionaries with it...
[DataContract]
public class MyClass
{
[DataMember]
private string _privateField;
[DataMember]
public int PublicProperty { get; set;}
}
Serialization :
private static string SerializeXml<T>(T item)
{
DataContractSerializer ser = new DataContractSerializer(item.GetType());
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings { OmitXmlDeclaration = true, ConformanceLevel = ConformanceLevel.Fragment };
using (XmlWriter writer = new XmlWriter(sb, settings))
{
ser.WriteObject(writer, item);
}
return sb.ToString();
}
Look here for differences between XmlSerializer and DataContractSerializer : http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/

The binaryformatter serializes private and even readonly fields without the need for properties. The XmlSerializer can only serialize with a public no-arg constructor and public properties. If you want to use XmlSerializer with encapsulation you can use IXmlSerializable, but that is rather painful.
If your object model is fairly simple or you can make it fairly simple by introducing special DTO:s for serialization (e.g. to avoid structs), then I recommend using a contract based serializer that can serialize private fields or properties. Have a look at protobuf-net.

Related

c# xmlSerializer fails because POCO generator creating ICollection objects

I have seen a lot of issues using the XmlSerializer on SO, and yes most of the issues seem to be around complex objects.
Mine issue is the same in essence, but with a twist.
I have used the Entityframework POCO generator to create my database objects.
Now, I am trying to compare complex objects using XmlSerializer.
So in my save, I am doing the following:
viewModelObj = returned object model from MVC page.
db.originalData.ToList() = original data object
var a = SerializeObject(viewModelObj);
var b = SerializeObject(db.originalData.ToList());
with the following definition for my SerializeObject function.
public static string SerializeObject(this List<myObject> toSerialize)
{
var xmlSerializer = new XmlSerializer(toSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, toSerialize);
return textWriter.ToString();
}
}
Because myObject repersents my POCO classes, Lists are defined as ICollection, which in turn causes the XmlSerializer to fail with the following message:
Cannot serialize member toSerialize of type System.Collections.Generic.ICollection`1[[myObject]] because it is an interface.
Which is the correct response.
Every time i run the EF POCO Generator, it changes everything back to ICollections, so my question is how can i use XmlSerializer using the POCO classes?
Update
Changed code from
public static string SerializeObject(this List<myObject> toSerialize)
to
public static string SerializeObject<T>(this List<T> toSerialize)
This is when I get the error message:
Cannot serialize member [object] of type System.Collection.Generic.ICollection'1[[object]] because it is an interface.
Just given up. re coded around this issue by adding a status field and checking to see if it has changed.

Can I deserialize generics without a reference to the type?

I am attempting to save/load a class to an xml file that contains generic types using a DataContractSerializer. I have the save working, but have realized I can't load it because I don't have the list of knownTypes for the deserializer.
Is there a way of serializing/deserializing this class that would allow me to deserialize it without referencing any of the stored types directly?
Here is my SessionVariables class that I am trying to save/load:
[DataContract]
public class SessionVariables
{
[DataMember]
private Dictionary<Type, ISessionVariables> _sessionVariables = new Dictionary<Type, ISessionVariables>();
private object _syncLock = new object();
public T Get<T>()
where T : ISessionVariables, new()
{
lock (_syncLock)
{
ISessionVariables vars = null;
if (_sessionVariables.TryGetValue(typeof(T), out vars))
return (T)vars;
vars = new T();
_sessionVariables.Add(typeof(T), vars);
return (T)vars;
}
}
public IList<Type> GetKnownTypes()
{
IList<Type> knownTypes = new List<Type>();
knownTypes.Add(this.GetType().GetType()); // adds System.RuntimeType
foreach (Type t in _sessionVariables.Keys)
{
if (!knownTypes.Contains(t))
knownTypes.Add(t);
}
return knownTypes;
}
}
The different modules of the application extend the ISessionVariables interface to create their own set of session variables, like this:
[DataContract]
public class ModuleASessionVariables : ISessionVariables
{
[DataMember]
public string ModuleA_Property1{ get; set; }
[DataMember]
public string ModuleA_Property2 { get; set; }
}
[DataContract]
public class ModuleBSessionVariables : ISessionVariables
{
[DataMember]
public string ModuleB_Property1{ get; set; }
[DataMember]
public string ModuleB_Property2 { get; set; }
}
And a singleton instance of the SessionVariables class is used to access session variables, like this:
singletonSessionVariables.Get<ModuleASessionVariables>().ModuleA_Property1
singletonSessionVariables.Get<ModuleBSessionVariables>().ModuleB_Property2
I got the save working like this:
using (FileStream writer = new FileStream(#"C:\test.txt", FileMode.Create))
{
DataContractSerializer dcs = new DataContractSerializer(typeof(SessionVariables), singletonSessionVariables.GetKnownTypes());
dcs.WriteObject(writer, singletonSessionVariables);
writer.Close();
}
However this method does not work to deserialize the class because I don't know it's known types.
Can I serialize and deserialize generic types when I don't have direct library references to any of the types used? And if so, how?
The problem here is that you aren't just wanting to serialize data, but you also want to serialize data about your data, i.e., (cue the dramatic chipmunk) metadata.
That metadata, in this case, are the types of the models that held the data originally. Normally, this isn't an issue, but as you've discovered if you're taking advantage of polymorphism in your design, your single collection may contain two or more different types, each of which needs to be deserialized to their original type.
This is usually accomplished by saving this Type metadata to the serialized result. Different serialization methods does this in different ways. Xaml serialization uses xml namespaces associated with .net namespaces, then names the elements after the original type name. Json.net accomplishes this via a specific named value saved to the json object.
The default DataContractSerializer is not Type aware. Therefore you need to replace it with a version that understands the .NET Type system and can serialize/deserialize Type metadata to the resulting xml. Luckily, one already exists in the framework, the NetDataContractSerializer.
And that's how you pad a link-only answer. The Aristocrats.
You could accomplish this using a custom DataContractResolver. This allows you to plug into the deserialization pipeline and provide a type to deserialize into based upon the type/namespace that is found in the serialized graph.
Here's a good article on it:
http://blogs.msdn.com/b/carlosfigueira/archive/2011/09/21/wcf-extensibility-data-contract-resolver.aspx
IDesign has an implementation of a resolver that can be used for dynamic discovery of types on their site: http://idesign.net/Downloads/GetDownload/1848 (you will probably have to make some modifications to handle generics)

This XmlWriter does not support base64 encoded data

I have a class like this:
public class Data
{
public string Name { get; set; }
public int Size { get; set; }
public string Value { get; set; }
[NonSerialized] public byte[] Bytes;
}
When a List<Data> hits the serialization method below, it occasionally dies with
InvalidOperationException "This
XmlWriter does not support base64
encoded data."
As you can see, I am not directly encoding anything, just using the default serialization mechanism.
private static XDocument Serialize<T>( T source )
{
var target = new XDocument( );
var s = new XmlSerializer( typeof( T ) );
using( XmlWriter writer = target.CreateWriter( ) )
{
s.Serialize( writer, source );
}
return target;
}
The data will have Name properties that are English words separated by underscores. The Value property will by similar except with added math operators or numbers (they are mathematical expressions).
Does anyone know what is causing it and how I can correct it?
Use [XmlIgnore] instead of [NonSerialized]. The latter is for the SOAP and binary formatters, according to MSDN:
When using the BinaryFormatter or
SoapFormatter classes to serialize an
object, use the NonSerializedAttribute
attribute to prevent a field from
being serialized. For example, you can
use this attribute to prevent the
serialization of sensitive data.
The target objects for the
NonSerializedAttribute attribute are
public and private fields of a
serializable class. By default,
classes are not serializable unless
they are marked with
SerializableAttribute. During the
serialization process all the public
and private fields of a class are
serialized by default. Fields marked
with NonSerializedAttribute are
excluded during serialization. If you
are using the XmlSerializer class to
serialize an object, use the
XmlIgnoreAttribute class to get the
same functionality.
Mind you, I'm surprised your original code even compiles - when I try it, it says that [NonSerialized] can only be applied to fields...

Is this too ... hacky? Xml to an object

I'm playing with my favorite thing, xml (have the decency to kill me please) and the ultimate goal is save it in-house and use the data in a different manner (this is an export from another system). I've got goo that works, but meh, I think it can be a lot better.
public Position(XElement element)
{
Id = element.GetElementByName("id");
Title = element.GetElementByName("title");
}
I'm thinking of making it more automated (hacky) by adding data annotations for the xml element it represents. Something like this for instance.
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("title")]
public string Title { get; set; }
then writing some reflection/mapper code but both ways feels ... dirty. Should I care? Is there a better way? Maybe deserialize is the right approach? I just have a feeling there's a mo' bettah way to do this.
You can use the XmlSerializer class and markup your class properties with attributes to control the serialization and deserialization of the objects.
Here's a simple method you can use to deserialize your XDocument into your object:
public static T DeserializeXml<T>(XDocument document)
{
using (var reader = document.CreateReader())
{
var serializer = new XmlSerializer(typeof (T));
return (T) serializer.Deserialize(reader);
}
}
and a simple serializer method:
public static String ToXml<T>(T instance)
{
using (var output = new StringWriter(new StringBuilder()))
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(output, instance);
return output.ToString();
}
}
The mechanism that you are suggesting already exists in the form of the XmlSerializer class and a set of custom attributes that you can use to control the serialisation process.
In fact the .Net framework comes with a tool xsd.exe which will generate these class files for you from a schema definition (.xsd) file.
Also, just to make things really easy, Visual Studio even has the ability to generate you an .xsd schema for an xml snippet (its hidden away in the Xml menu somewhere).

WCF serialization problem using NameValueCollection

I'm trying to serialize a NameValueCollection over WCF. I keep getting exceptions telling me to add one type after another. After adding them, I finally get
Type 'System.Object[]' cannot be added to list of known types since another type 'System.Collections.ArrayList' with the same data contract name 'http://schemas.microsoft.com/2003/10/Serialization/Arrays:ArrayOfanyType' is already present.
The contract now looks like this:
[KnownType(typeof(NameValueCollection))]
[KnownType(typeof(CaseInsensitiveHashCodeProvider))]
[KnownType(typeof(CaseInsensitiveComparer))]
[KnownType(typeof(string[]))]
[KnownType(typeof(Object[]))]
[KnownType(typeof(ArrayList))]
[DataContract]
public class MyClassDataBase
{
[DataMember]
public NameValueCollection DataCollection = new NameValueCollection();
}
I really dont know what to do to be able to serialize my NameValueColletion.
Another strange thing is that the compiler warns that the CaseInsensitiveHashCodeProvider is deprecated.
The best idea would be to stop using weak types like NameValueCollection and ArrayList. Use Dictionary<string,string> and List<T> instead.
The NameValueCollection doesn't seem exactly to be designed for this use case. There is a series of blog posts on this blog that deals with this problem and presents a possible solution (to wit: use an IDictionary). I haven't tested it though.
Another approach could be to use a wrapper class to adapt NameValueCollection to WCF serialization. Here's a simple example that serializes each name-value pair as a single, comma-delimited string. It then reads that value back into the NameValueCollection upon deserialization:
[CollectionDataContract]
public class NameValueCollectionWrapper : IEnumerable
{
public NameValueCollectionWrapper() : this(new NameValueCollection()) { }
public NameValueCollectionWrapper(NameValueCollection nvc)
{
InnerCollection = nvc;
}
public NameValueCollection InnerCollection { get; private set; }
public void Add(object value)
{
var nvString = value as string;
if (nvString != null)
{
var nv = nvString.Split(',');
InnerCollection.Add(nv[0], nv[1]);
}
}
public IEnumerator GetEnumerator()
{
foreach (string key in InnerCollection)
{
yield return string.Format("{0},{1}", key, InnerCollection[key]);
}
}
}
This would allow you to use the type as a standard [DataMember] property on your DataContracts and you would also use standard WCF serialization techniques.
NameValueCollection does not directly implement the ICollection interface. Instead, NameValueCollection extends NameObjectCollectionBase. This implements the ICollection interface, and the overloaded Add(system.string) method is not implemented in the NameValueCollection class. When you use the XMLSerializer, the XmlSerializer tries to serialize or deserialize the NameValueCollection as a generic ICollection. Therefore, it looks for the default Add(System.String). In the absence of the Add(system.String) method, the exception is thrown.
Use the SoapFormatter class for serialization instead of using XML Serialization. To use the SoapFormatter class, you must add a reference to System.Runtime.Serialization.Formatters.Soap.dll.
// Serializing NameValueCollection object by using SoapFormatter
SoapFormatter sf = new SoapFormatter();
Stream strm1 = File.Open(#"C:\datasoap.xml", FileMode.OpenOrCreate,FileAccess.ReadWrite);
sf.Serialize(strm1,namValColl);
strm1.Close();
// Deserializing the XML file into NameValueCollection object
// by using SoapFormatter
SoapFormatter sf1 = new SoapFormatter();
Stream strm2 = File.Open(#"C:\datasoap.xml", FileMode.Open,FileAccess.Read);
NameValueCollection namValColl1 = (NameValueCollection)sf1.Deserialize(strm2);
strm2.Close();

Categories