Serialization design problem relating to custom objects
Hi,
I am developing a modeling application where custom objects would be created by certain external assemblies. These assemblies would be picked up on the fly by using reflection.
In my application, I have defined an abstract class ObjectBase, which implements ISerializable. The custom objects would derive from this class.
Lets say that a model is created, and a custom object from an external assembly is added to the model. Now we save the model. Saving is implemented using binary serialization. Now, I give the saved model file to a friend, who does not have the assembly present on his machine. In such a case, deserializing the model on his machine would fail. To overcome this problem, I have defined a placeholder object, called ErrorObject (deriving from ObjectBase), and implemented a serialization binder. During deserialization the binder checks if the assembly exists and if it doesnt, then binds the custom object type to the Error object. Now I am able to extract all the data relevant to the base object when the error object gets deserialized. When the error object is serialized again, I update the serialization info object so that the object gets serialized as the same old custom object. This would allow successful deserialization of the custom object in future in case the assembly becomes available on the friend's machine.
This strategy would work fine as long as the derived custom objects dont need any serializable data of their own. If a derived custom object defines its own serializable data, then that data will be lost when attempting to deserialize after the absent assembly becomes available.
I have been thinking of defining a collection of name-value pair in the base class, which the derived class could use to store its data needing to be persisted. But I am looking for a better solution if possible. Or does someone have a better design solution to this problem?
Thanks in advance,
Mayur
[1990 byte] By [
MayurK] at [2008-1-3]
Hi Mayur,
This sounds a good idea. I am actually trying to do the same thing. But not sure how do to these two steps in .NET 2.0
During deserialization the binder (BindToType) binds the custom object type to the Error object. (I can return an ErrorObject with basic properties)
When the error object is serialized again, I update the serialization info object so that the object gets serialized as the same old custom object. (How do i preseve the original type name and additional fields while seriaIizing it back. So that when a version with this new type read this serializaed data, it can serialize it proeprly without loosing anything)
I am using custom serialization with binders.
Any help would be greatly appreciated.
Thanks,
Salim
A sample:
namespace
Program {
public class Program {
public class MySerializationBinder : SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
return typeof(Bar); }
}
[Serializable]
public class Foo : ISerializable {
public int x; public Foo() {
}
protected Foo(SerializationInfo info, StreamingContext context) {
x = info.GetInt32(
"x"); }
#region
ISerializable Members public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(
"x", x); }
#endregion
}
[Serializable]
public class Bar : ISerializable {
private readonly SerializationInfo serializationInfo; public Bar() {
}
protected Bar(SerializationInfo info, StreamingContext context) {
serializationInfo = info;
}
#region
ISerializable Members public void GetObjectData(SerializationInfo info, StreamingContext context) {
foreach(SerializationEntry entry in serializationInfo) {
info.AddValue(entry.Name, entry.Value, entry.ObjectType);
}
info.SetType(
typeof(Foo)); }
#endregion
}
static void Main() {
BinaryFormatter formatter =
new BinaryFormatter(); formatter.Binder =
new MySerializationBinder(); Foo foo =
new Foo(); foo.x = 345;
Bar bar;
using(FileStream file = File.Create(@"C:\test.bin")) {
formatter.Serialize(file, foo);
file.Position = 0;
bar = (Bar) formatter.Deserialize(file);
file.Position = 0;
formatter.Serialize(file, bar);
file.Position = 0;
formatter.Binder =
null; foo = (Foo) formatter.Deserialize(file);
}
}
}
}
Hi Lucian,
Your solution will not work in my case since the definition of Foo will not be available when serializing the object of type Bar (in Bar.GetObjectData()). This would result in an exception.
If the derived class (such as Foo) is using any member of a type whose definition is not available when deserializing the object as Bar (since the assembly defining Foo and the types for one or more of its members is not present on the machine), the technique you have mentioned will not work.
Please let me know if I am missing something here.
Thanks for your reply.
Regards,
Mayur
Hi Salim,
My base class ObjectBase has the following code in the GetObjectData method:
public
virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
if (string.IsNullOrEmpty(m_DerivedAssemblyName))
{
m_DerivedAssemblyName = info.AssemblyName;
m_DerivedTypeName = info.FullTypeName;
}
....
}
This would preserve the assembly name and type of the derived object when the object is serialized for the first time.
The ErrorObject has the following code:
public
override void GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
info.AssemblyName = m_DerivedAssemblyName ;
info.FullTypeName = m_DerivedAssemblyTypeName ;
}
In this way, I can serialize the Error object back as the original derived object, but as I have mentioned before, I would lose the values of any extra fields in the derived object.
Regards,
Mayur
I didn't try to provide a complete solution. Just the interesting part :). A complete solution will have to check before deserializing whether the respective type exists on that machine. If it doesn't, then it will have to map it to the general type (Bar in this case). So in Bar.GetObjectData, you would call SetType with the real type Foo only if it can be found by the runtime. I think that the same can be done for members of Foo that are other non existent types.
Hi Mayur,
I am looking for a solution that serializes and desterilizes derived objects that are defined in separate assembly. Would you be able to shade some light on that since you have already done it in this problem?
Thanks, -Manish
Hi Manish,
Do you mean to say that the type to be serialized is present in a separate assembly and is derived from a base type defined in your assemby? If that is the case, the solution is straight forward. You just need to hold the reference to the derived type being serialized in a variable of the base type and serialize the object. Later, deserialize the stream into a base type variable. The base type variable would then reference the correct derived object. The binary formatter takes care of serializing and deserializing the correct object.
If I have got your question wrong, do let me know.
Regards,
Mayur
Hi Mayur,
You got the question right. Also the base is an abstract class and is not aware of type of derived objects.
I tried that but couldn't make it to work. I get "{"The type ClassLibrary1.myRequest was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."}" even after holding the reference of the derived class in a variable of the base type.
Here is my library:
namespace MainClassLib
{
[Serializable]
public class ImportSet
{
#regionFields
private ImportRequest _request;
private ImportResponse _response;
#endregion
#region Properties
public ImportRequest Request
{
get { return _request; }
set { _request = value; }
}
public ImportResponse Response
{
get { return _response; }
set { _response = value; }
}
#endregion
}
[Serializable]
[XmlInclude(typeof(FileRequest))]
public abstract class ImportRequest
{
protected ImportRequest()
{
}
}
[Serializable]
public abstract class FileRequest : ImportRequest
{
public FileRequest()
{
}
public abstract FileResponse Process();
}
[Serializable]
[XmlInclude(typeof(FileResponse))]
public abstract class ImportResponse
{
#regionConstructors
protected ImportResponse()
{
}
#endregion
}
[Serializable]
public abstract class FileResponse : ImportResponse
{
#region Properties
public abstract string ConnectionString();
#endregion
#regionConstructors
public FileResponse()
{
}
#endregion
}
}
And here is derived class:
namespace ClassLibrary1
{
[Serializable]
public class myRequest : FileRequest
{
[System.Xml.Serialization.XmlElement(ElementName = "Directory")]
public string Directory
{
get { return _directory; }
set { _directory = value;}
}
string _directory;
public override FileResponse Process()
{
throw new Exception("The method or operation is not implemented.");
}
}
}
Thanks, -Manish
Hi Manish,
I thought you were using the binary formatter. But it appears that you are using the xml serializer or something similar. May be, if it is possible for you to use binary serialization instead of xml serialization, your problem will be solved.
In order to serialize and deserialize successfully using XmlSerializer, I guess you will need to construct the XmlSerializer for the correct type (the type of the derived object) using the constructor. This is easy during serialization since you can get the type information from the object being serialized, but during deserialization the information might not be readily available. If you could somehow arrange for this information to be present during deserialization (even the type name), the approach will work.
Regards,
Mayur
Thanks, Mayur. I got it working using Binary Formatter. I am still investigating on XmlSerializer approach. I will post if I find any solution.
Thanks again. -Manish
Reporting back my findings - it seems like XmlSerializer needs to be aware of type of objects its serializing. You cannot have object whoes type is not known. For such situation, you can use Binary Formatter. Binary Formatter will serialize such objects along with it's assembly name so it can be deserialized.
I could be wrong as I am still learning more about serialization.