views:

56

answers:

3

Hi,

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 :

A: 

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.

Sergey Teplyakov
Thank you for your answer but then, how would I use serialization surrogates? How can I register it?
Guillaume
Consider Jeffrey Richter's article Run-time Serialization, Part 3: http://msdn.microsoft.com/en-us/magazine/cc188950.aspx. But I really not sure that this is a good solution.
Sergey Teplyakov
A: 

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);
    }
  }
Guillaume
I don't really think that this is good idea. It's violates all exception handling principles where exception handling strategy depends on exception type. In your case you slice all your exception types to CommonException. In this case is much easier to throw CommonException and add message that contains all appropriate information.
Sergey Teplyakov
My Library1 exception handling can't have knowledge of Library2 exception types... only Library1 and Common exception types.
Guillaume
In this case simply throw CommonException from Library2 (because in any case you can't properly handle all other exceptions).
Sergey Teplyakov
I don't see why I can't properly handle an exception that inherit CommonException if I can handle CommonException. If it's the case, the exception shouldn't inherit CommonException at all.
Guillaume
A: 

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...

Hope this helps, Best regards, Tom.

tommieb75
Thanks but BinarySerializer wont help for my issue with AppDomain.
Guillaume