Deserializing in a different assembly... how to?
-
Hi! I have 2 different applications/assembly. The first one creates an object and serializes it to a textfile on disk. The second one has the the exact same class (copied/pasted). It reads the file that is on disk and attempts to deserialize it and create a new object. The process works fine if I serialize/deserialize in the first application, but it fails when I try to deserialize in the second application. Here's the exception: Parse Error, no assembly associated with Xml key a1:http://schemas.microsoft.com/clr/nsassem/UI/UI%2C Version%3D1.0.1419.30011%2C Culture%3Dneutral%2C PublicKeyToken%3Dnull AdonisConfigDisk Although it is generally not a good idea to copy/paste code like I did (I should have 1 dll with the code I need), I am forced to do so for some reasons I won't go into right now. Here's my code... Does anyone have an idea of what I'm doing wrong? How can I strip the assembly information from the serialized object?
public MemoryStream Serialize() { MemoryStream stream = New MemoryStream(); SoapFormatter sf = New SoapFormatter(null, New StreamingContext(StreamingContextStates.All)); sf.Serialize(stream, this); stream.Seek(0, 0); return stream; } public AdonisConfigDisk Deserialize(MemoryStream ms) { SoapFormatter sf = New SoapFormatter(null, New StreamingContext(StreamingCOntextStates.All)); return DirectCast(sf.Deserialize(ms), AdonisConfigDisk); }
Thanks! Carl -
Hi! I have 2 different applications/assembly. The first one creates an object and serializes it to a textfile on disk. The second one has the the exact same class (copied/pasted). It reads the file that is on disk and attempts to deserialize it and create a new object. The process works fine if I serialize/deserialize in the first application, but it fails when I try to deserialize in the second application. Here's the exception: Parse Error, no assembly associated with Xml key a1:http://schemas.microsoft.com/clr/nsassem/UI/UI%2C Version%3D1.0.1419.30011%2C Culture%3Dneutral%2C PublicKeyToken%3Dnull AdonisConfigDisk Although it is generally not a good idea to copy/paste code like I did (I should have 1 dll with the code I need), I am forced to do so for some reasons I won't go into right now. Here's my code... Does anyone have an idea of what I'm doing wrong? How can I strip the assembly information from the serialized object?
public MemoryStream Serialize() { MemoryStream stream = New MemoryStream(); SoapFormatter sf = New SoapFormatter(null, New StreamingContext(StreamingContextStates.All)); sf.Serialize(stream, this); stream.Seek(0, 0); return stream; } public AdonisConfigDisk Deserialize(MemoryStream ms) { SoapFormatter sf = New SoapFormatter(null, New StreamingContext(StreamingCOntextStates.All)); return DirectCast(sf.Deserialize(ms), AdonisConfigDisk); }
Thanks! CarlThe problem is that when you serialize an object, its
Type
is saved so that it can be deserialized to the proper object type later. In your error, that should now be clear. A Type includes the namespace, class name, assembly name, culture, version, and public key tokens. So, you can deserialize an object from another assembly...unless that assembly has the other assembly loaded! When the object is being deserialized in the first assembly, the type references a type in that assembly so it's not a problem. When the other assembly tries to do it, the assembly in which the Type is defined may not be loaded. To make sure it is, get the currentAppDomain
in your second assembly (the one having problems) and callAppDomain.Current.Load
(several overloads, see MSDN docs for details). This should work okay then.-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
The problem is that when you serialize an object, its
Type
is saved so that it can be deserialized to the proper object type later. In your error, that should now be clear. A Type includes the namespace, class name, assembly name, culture, version, and public key tokens. So, you can deserialize an object from another assembly...unless that assembly has the other assembly loaded! When the object is being deserialized in the first assembly, the type references a type in that assembly so it's not a problem. When the other assembly tries to do it, the assembly in which the Type is defined may not be loaded. To make sure it is, get the currentAppDomain
in your second assembly (the one having problems) and callAppDomain.Current.Load
(several overloads, see MSDN docs for details). This should work okay then.-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
Heath, Thanks for your answer. However, it does not answer my question since I am not distributing the first assembly with the second. Both objects are the exact same, though (copy/paste). Is there a way to "force" the deserialization even if the originating assembly is different? Thank you! Carl
-
Heath, Thanks for your answer. However, it does not answer my question since I am not distributing the first assembly with the second. Both objects are the exact same, though (copy/paste). Is there a way to "force" the deserialization even if the originating assembly is different? Thank you! Carl
If you can't include the assembly that has the type definition for the object you want to deserialize, you're either have to modify the string/binary data to change the assembly name (very dangerous!) or have a common library that defines that type that both assemblies you mentioned reference. The latter method would be much preferred.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
If you can't include the assembly that has the type definition for the object you want to deserialize, you're either have to modify the string/binary data to change the assembly name (very dangerous!) or have a common library that defines that type that both assemblies you mentioned reference. The latter method would be much preferred.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
If I can't find a solution, I'm scared I will have to change the SOAP data in code... not very elegant but if it does the trick...
Again, I gave a solution that doesn't require changing SOAP: create a new library that contains the Type in question and use the Type out of that library in both assemblies that you mentioned earlier. That way both previously mentioned assemblies are independent of each other but share a common Type which both can serialize(/deserialize). This is an easy solution.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
Again, I gave a solution that doesn't require changing SOAP: create a new library that contains the Type in question and use the Type out of that library in both assemblies that you mentioned earlier. That way both previously mentioned assemblies are independent of each other but share a common Type which both can serialize(/deserialize). This is an easy solution.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
Heath, Thanks for your replies! I finally managed to make it work by reflecting all the properties of my class into a dataset and exporting the dataset to xml. This is powerful enough for my needs. I have encapsulated everything pretty well and it works like a charm now! Thanks again! Carl
-
Heath, Thanks for your replies! I finally managed to make it work by reflecting all the properties of my class into a dataset and exporting the dataset to xml. This is powerful enough for my needs. I have encapsulated everything pretty well and it works like a charm now! Thanks again! Carl
So now you take the most inefficient method for serializing an object? Seriously, it's not a hard solution and this is a typically problem that modular programming is meant to solve. Just put the Type in a different assembly that both the first and second assemblies reference. This is a proper solution and what libraries are meant to do. You can do it the way you want, but it's terribly inefficient.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
So now you take the most inefficient method for serializing an object? Seriously, it's not a hard solution and this is a typically problem that modular programming is meant to solve. Just put the Type in a different assembly that both the first and second assemblies reference. This is a proper solution and what libraries are meant to do. You can do it the way you want, but it's terribly inefficient.
-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
Imagine that there are already some serialized objects and I want to change the namespace for an application. How can I do it? Is there a way to do that? I do not know how to exclude the namespace information while serializing or deserializing. Does anyone knows if it is also a subject in binary serialization.
-
Imagine that there are already some serialized objects and I want to change the namespace for an application. How can I do it? Is there a way to do that? I do not know how to exclude the namespace information while serializing or deserializing. Does anyone knows if it is also a subject in binary serialization.
It would have to, otherwise how would deserialization know which object to create to hold the following data? This is not unfounded. Even in COM serialization (for instance, in compound documents format liks Word) the CLSID is stored for embedded objects so that they can be reconstituted, then they are handed various interfaces and perform their own persistence based on what interfaces they implement (and if the contianer application supports that persistence interface, like
IPersistStorage
). You could have a conversion program to upgrade your serialized data object, either by manipulating the data stream itself or using the Type in your old assembly with a similar Type in your new assembly (the common assembly) and deserialize the old Type, clone the data to the new Type, and serialize that. Next time, you should be careful to design your assemblies in such a way that common functionality and types are in a common library, rather than duplicating such functionality and types in two different assemblies. As you can see, this is especially a problem when remoting.-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
-
It would have to, otherwise how would deserialization know which object to create to hold the following data? This is not unfounded. Even in COM serialization (for instance, in compound documents format liks Word) the CLSID is stored for embedded objects so that they can be reconstituted, then they are handed various interfaces and perform their own persistence based on what interfaces they implement (and if the contianer application supports that persistence interface, like
IPersistStorage
). You could have a conversion program to upgrade your serialized data object, either by manipulating the data stream itself or using the Type in your old assembly with a similar Type in your new assembly (the common assembly) and deserialize the old Type, clone the data to the new Type, and serialize that. Next time, you should be careful to design your assemblies in such a way that common functionality and types are in a common library, rather than duplicating such functionality and types in two different assemblies. As you can see, this is especially a problem when remoting.-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----
I found the solution and it solves lots of problems. :) You are right. How would deserialization know which object to create to hold the data. So there must be a way to tell the deserialization which object to create. And there is such a way. While deserializing it is trying to create the object in the serialized data. But I want to create my new object. So I wrote my special binder for my object. public class SpecialBinder : System.Runtime.Serialization.SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { MyObject obj=new MyObject(); return obj.GetType(); } I binded my new binder object for deserialization. So I told the deserialization to create my new object. It may whether be under a new name space or even in a new assembly. soapFormatter.Binder=new MyBinder(); MyObject obj=(MyObject)formatter.Deserialize(myStream); What if there are multiple objects that are marked as serialized in the serialized object. At least now I wrote the solution. It should return all the classes that are serialized in the main serialized object. public override Type BindToType(string assemblyName, string typeName) { MyObject1 obj1=new MyObject1(); MyObject2 obj2=new MyObject2(); MyObject3 obj3=new MyObject3(); if (typeName.IndexOf("MyObject1")>0){ return obj1.GetType(); } if (typeName.IndexOf("MyObject2")>0){ return obj2.GetType(); } if (typeName.IndexOf("MyObject3")>0){ return obj3.GetType(); } return null; } This approach works in both binary and soap serialization. I will try to write a generic approach. Then I can share it with you. I know it is not a good idea to do such things but in my case I have to do that.
-
I found the solution and it solves lots of problems. :) You are right. How would deserialization know which object to create to hold the data. So there must be a way to tell the deserialization which object to create. And there is such a way. While deserializing it is trying to create the object in the serialized data. But I want to create my new object. So I wrote my special binder for my object. public class SpecialBinder : System.Runtime.Serialization.SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { MyObject obj=new MyObject(); return obj.GetType(); } I binded my new binder object for deserialization. So I told the deserialization to create my new object. It may whether be under a new name space or even in a new assembly. soapFormatter.Binder=new MyBinder(); MyObject obj=(MyObject)formatter.Deserialize(myStream); What if there are multiple objects that are marked as serialized in the serialized object. At least now I wrote the solution. It should return all the classes that are serialized in the main serialized object. public override Type BindToType(string assemblyName, string typeName) { MyObject1 obj1=new MyObject1(); MyObject2 obj2=new MyObject2(); MyObject3 obj3=new MyObject3(); if (typeName.IndexOf("MyObject1")>0){ return obj1.GetType(); } if (typeName.IndexOf("MyObject2")>0){ return obj2.GetType(); } if (typeName.IndexOf("MyObject3")>0){ return obj3.GetType(); } return null; } This approach works in both binary and soap serialization. I will try to write a generic approach. Then I can share it with you. I know it is not a good idea to do such things but in my case I have to do that.
Hmm, I didn't know about the serialization binder. Very interesting. And speaking of interesting serialization things, you should check out the
ISerializationSurrogate
. It won't help you, but it may come in handy for other things. Just thought I'd mention it. :) One thing, though, I wouldn't returnnull
fromBindToType
. Instead, you could returnType.GetType(string.Format("{0}, {1}", typeName, assemblyName)
, or else nested objects or other objects won't be (de)serialized (unless returningnull
tells the serializer to use the actual type, but it's not documented and I don't feel like digging through the IL to find it! :eek: ).-----BEGIN GEEK CODE BLOCK----- Version: 3.21 GCS/G/MU d- s: a- C++++ UL@ P++(+++) L+(--) E--- W+++ N++ o+ K? w++++ O- M(+) V? PS-- PE Y++ PGP++ t++@ 5 X+++ R+@ tv+ b(-)>b++ DI++++ D+ G e++>+++ h---* r+++ y+++ -----END GEEK CODE BLOCK-----