Cross AppDomain Exception serialization
Given two app domain : in the first, Library1 and CommonLibrary are loaded. In the second Library2 and CommonLibrary are loaded.
Library2 defines a Library2Exception that inherit from CommonException (defined in CommonLibrary). When I call, in the first AppDomain, a method on a MarshallByRef of the second AppDomain that throws a Library2Exception, a SerializationException is thrown.
Indeed, .Net tries to deserialize Library2Exception but this type is defined in Library2 which is not found in the first AppDomain. I want it to become a CommonException that I can handle.
So, my questions are :
- How can we control serialization between AppDomain like a SerializationBinder would do on a BinaryFormatte开发者_开发问答r?
- Is it possible to have an exception ByRef instead of ByValue (serialized) ?
I found! Override GetObjectData to change the exception type :
[Serializable]
public class CommonException : Exception
{
public CommonException() { }
public CommonException(string message)
: base(message) { }
public CommonException(string message, Exception inner)
: base(message, inner) { }
protected CommonException(
SerializationInfo info,
StreamingContext context)
: base(info, context)
{ }
public override void GetObjectData(
SerializationInfo info,
StreamingContext context)
{
if (context.State == StreamingContextStates.CrossAppDomain)
info.SetType(typeof(CommonException));
base.GetObjectData(info, context);
}
}
You should either load Library2 in first appdomain or you should throw some exception that defined in CommonLibrary.
P.S. Exceptions throwns by reference (inside one app domain), because they are reference types, but they are thrown "by value" between different app domains (because they are not MarshalByRef descendants), and you can't change this behavior. Cosider:
//Oops! I can't do that!
public class MyException : Exception, MarshalByRef
{
}
P.S.S. You may use serialization surrogates or something like that to solve your problem, but I think it much cleaner and easier explicitly throw common exception type.
Here's an example of a Serialization Binder as you have asked for, this routine is a custom 'Serialize' with a 'SerializationBinder' used as its parameter
// ... This is a class object of type Foo...
public bool Serialize(string sPath, System.Runtime.Serialization.SerializationBinder serializationBinder) {
bool bSuccessful = false;
if (serializationBinder == null) return false;
try {
using (FileStream fStream = new FileStream(sPath, FileMode.Create)) {
try {
BinaryFormatter bf = new BinaryFormatter();
bf.Binder = serializationBinder;
bf.Serialize(fStream, this._someFoo);
bSuccessful = true;
} catch (System.Runtime.Serialization.SerializationException sEx) {
System.Diagnostics.Debug.WriteLine(sEx.ToString());
bSuccessful = false;
}
}
} catch (System.IO.IOException ioEx) {
System.Diagnostics.Debug.WriteLine(string.Format("[Serialize(...)] - IO EXCEPTION> DETAILS ARE {0}", ioEx.ToString()));
bSuccessful = false;
}
return bSuccessful;
}
public bool Deserialize(string sFileName, System.Runtime.Serialization.SerializationBinder serializationBinder) {
bool bSuccessful = false;
//
if (!System.IO.File.Exists(sFileName)) return false;
if (serializationBinder == null) return false;
this._foo = new Foo();
//
try {
using (FileStream fStream = new FileStream(sFileName, FileMode.Open)) {
try {
BinaryFormatter bf = new BinaryFormatter();
bf.Binder = serializationBinder;
this._foo = (Foo)bf.Deserialize(fStream);
bSuccessful = true;
} catch (System.Runtime.Serialization.SerializationException sEx) {
System.Diagnostics.Debug.WriteLine(string.Format("[DeSerialize(...)] - SERIALIZATION EXCEPTION> DETAILS ARE {0}", sEx.ToString()));
bSuccessful = false;
}
}
} catch (System.IO.IOException ioEx) {
System.Diagnostics.Debug.WriteLine(string.Format("[DeSerialize(...)] - IO EXCEPTION> DETAILS ARE {0}", ioEx.ToString()));
bSuccessful = false;
}
return (bSuccessful == true);
}
// End class method for object class type Foo
public class BarBinder : System.Runtime.Serialization.SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
Type typeToDeserialize = null;
try {
// For each assemblyName/typeName that you want to deserialize to
// a different type, set typeToDeserialize to the desired type.
string assemVer1 = System.Reflection.Assembly.GetExecutingAssembly().FullName;
if (assemblyName.StartsWith("Foo")) {
assemblyName = assemVer1;
typeName = "FooBar" + typeName.Substring(typeName.LastIndexOf("."), (typeName.Length - typeName.LastIndexOf(".")));
}
typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
} catch (System.Exception ex1) {
throw ex1;
} finally {
}
return typeToDeserialize;
}
}
And is invoked like this:
_foo.DeSerialize(@"C:\foo.dat", new BarBinder());
What happens there is when 'BarBinder' is instantiated and assigned to the BinaryFormatter's Binder property, since the data serialized has type-name Foo.SomeClass, we applied the 'BarBinder', renaming the type-name to 'FooBar.SomeClass' effectively making the data to belong to another type...
精彩评论