BinaryFormatter deserialize gives SerializationException
I'm getting an:
System.Runtime.Serialization.SerializationException: Unable to find assembly 'myNameSpace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
When trying to deserialize some data in another program than the program I serialized it with.
After some googling I've found out that apparently this can only be done using a shared assembly.
However, my database is full with this serialized objects, and I need a utility program to get them out. Is there a way to override this behavior and just feed it the exact same class and force it do deserialize?
I already found this snippet, but I don't understand how and where I should put/use this.
static constructor() {
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
Assembly ayResult = null;
string sShortAssemblyName = args.Name.Split(',')[0];
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly ayAssembly in ayAssemblies) {
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) {
开发者_如何学编程 ayResult = ayAssembly;
break;
}
}
return ayResult;
}
You can get around this issue without needing the DLL if you know the object...
http://spazzarama.com/2009/06/25/binary-deserialize-unable-to-find-assembly/
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationbinder(VS.71).aspx
Use the “System.Runtime.Serialization.SerializationBinder” class. By inheriting from this class it is possible to redirect all the requests for types from the binary formatter to the types of your choice.
Here is a sample that will allow the types to be found in the current assembly regardless of which version of the assembly originally created the serialized stream:
sealed class AllowAllAssemblyVersionsDeserializationBinder : System.Runtime.Serialization.SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
String currentAssembly = Assembly.GetExecutingAssembly().FullName;
// In this case we are always using the current assembly
assemblyName = currentAssembly;
// Get the type using the typeName and assemblyName
Type typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, assemblyName));
return typeToDeserialize;
}
}
public static MyRequestObject Deserialize(byte[] b)
{
MyRequestObject mro = null;
var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (var ms = new System.IO.MemoryStream(b))
{
// To prevent errors serializing between version number differences (e.g. Version 1 serializes, and Version 2 deserializes)
formatter.Binder = new AllowAllAssemblyVersionsDeserializationBinder();
// Allow the exceptions to bubble up
// System.ArgumentNullException
// System.Runtime.Serialization.SerializationException
// System.Security.SecurityException
mro = (MyRequestObject)formatter.Deserialize(ms);
ms.Close();
return mro;
}
}
You will need to provide a reference to the original type somehow so that the utility program knows how to deserialize it.
The easy way is just to add the DLL the types were originally defined in as a reference to the utility project.
The code you posted allows you to dynamically load that same DLL when the deserializer determines it can't find the type. This is a more difficult approach (but not that difficult), but in both cases you will need a DLL that defines the types... so probably easiest just to statically link by adding the reference.
If your types are not currently in a DLL (e.g. if they are in an EXE), I suggest you pull the classes out of the EXE into a new DLL, and reference that DLL both from the original project and from the util project.
I ran into a similar problem and I got it working with help of the following link: BinaryFormatterDeserialize-not-finding-a-type
Basically what you need to do is subscribe to the AssemblyResolve event BEFORE deserializing. Then unsubscribe after deserialization..
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(CurrentDomain_AssemblyResolve);
// CODE TO DESERIALIZE HERE
AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Here the method I used to resolve the Assembly:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
if(args.Name == "MY ASSEMBLY NAME"))
{
//Load my Assembly
Assembly assem = Assembly.LoadFrom("MY ASSEMBLY PATH");
if(assem != null)
return assem;
}
}
catch { ;}
return Assembly.GetExecutingAssembly();
}
JTtheGeek's answer is correct that a custom SerializationBinder
is the way to handle this problem. The example code given in that answer isn't sufficient for my use case, though. I needed something that could:
- Handle mismatched version numbers and namespaces.
- Look in all assemblies, not just the current one.
- Still be fast enough to call 100 times per second.
This is what I came up with:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Company.Product.Common.Serialize
{
/// <summary>
/// A Serialization Binder that allows inexact matches (version number or namespace).
/// </summary>
public sealed class AllowInexactMatchSerializationBinder : System.Runtime.Serialization.SerializationBinder
{
static private Dictionary<string, Type> typeBindings = new Dictionary<string, Type>();
/// <summary>
/// When overridden in a derived class, controls the binding of a serialized object to a type.
/// </summary>
/// <param name="assemblyName">Specifies the <see cref="T:System.Reflection.Assembly" /> name of the serialized object.</param>
/// <param name="typeName">Specifies the <see cref="T:System.Type" /> name of the serialized object.</param>
/// <returns>
/// The type of the object the formatter creates a new instance of.
/// </returns>
public override Type BindToType(string assemblyName, string typeName)
{
Type type;
var assemblyQualifiedTypeName = String.Format("{0}, {1}", typeName, assemblyName);
// use cached result if it exists
if (typeBindings.TryGetValue(assemblyQualifiedTypeName, out type))
{
return type;
}
// try the fully qualified name
try { type = Type.GetType(assemblyQualifiedTypeName); }
catch { type = null; }
if (type == null)
{
// allow any assembly version
var assemblyNameWithoutVersion = assemblyName.Remove(assemblyName.IndexOf(','));
var assemblyQualifiedTypeNameWithoutVersion = String.Format("{0}, {1}", typeName, assemblyNameWithoutVersion);
try { type = Type.GetType(assemblyQualifiedTypeNameWithoutVersion); }
catch { type = null; }
}
if (type == null)
{
// check all assemblies for type full name
try
{
type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.ExportedTypes)
.Where(a => a.FullName == typeName)
.FirstOrDefault();
}
catch { type = null; }
}
if (type == null)
{
// check all assemblies for type name
var name = typeName.Split('.').Last();
try
{
type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.ExportedTypes)
.Where(a => a.Name == name)
.FirstOrDefault();
}
catch { type = null; }
}
typeBindings[assemblyQualifiedTypeName] = type;
return type;
}
}
}
If you don't have access to the original assembly that serialized the data, then you can use a SerializationBinder or a SerializationSurrogate. These two interfaces allow you to control how types are converted between one another when deserializing.
I followed the solution answered by JTtheGeek and in order to get it work for me I had to add the following just before the statement assemblyName = currentAssembly;
:
typeName = "yourNamespace.yourClassName";
After that it worked great!
精彩评论