跨应用程序域异常序列化

4
给定两个应用程序域: 第一个加载Library1和CommonLibrary。第二个加载Library2和CommonLibrary。
Library2定义了一个继承自CommonException(在CommonLibrary中定义)的Library2Exception。 当我在第一个AppDomain中调用第二个AppDomain的MarshallByRef上的方法并抛出Library2Exception时,会抛出SerializationException。
实际上,.Net尝试反序列化Library2Exception,但是这个类型在第一个AppDomain中找不到Library2。我希望它变成一个CommonException,这样我就可以处理它了。
所以,我的问题是:
- 我们如何控制 AppDomain之间的序列化,就像 BinaryFormatter上的 SerializationBinder一样? - 是否可能有一个ByRef而不是ByValue(序列化)的异常?

请阅读我的博客,了解跨应用程序域通信的内容:https://blog.vcillusion.co.in/sending-events-through-application-domain-boundary/ - vCillusion
3个回答

4

我发现了!重写GetObjectData方法可以更改异常类型:

  [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);
    }
  }

我并不认为这是一个好主意。它违反了所有异常处理原则,其中异常处理策略取决于异常类型。在你的情况下,你将所有的异常类型都切片到CommonException中。在这种情况下,更容易抛出CommonException并添加包含所有适当信息的消息。 - Sergey Teplyakov
我的Library1异常处理不能了解Library2的异常类型...只能了解Library1和Common的异常类型。 - Guillaume
在这种情况下,只需从Library2抛出CommonException(因为无论如何都不能正确处理所有其他异常)。 - Sergey Teplyakov
如果我可以处理CommonException,我不明白为什么我不能正确处理继承自CommonException的异常。如果是这种情况,那么异常根本不应该继承CommonException。 - Guillaume
Sergey的观点是,如果你要截断异常类,为什么还要使用派生异常类呢?不要设计一个需要模糊或奇怪技巧才能产生简单结果的系统,直接从一开始就使用简单操作。换句话说,如果你想让Lib1处理由Lib2抛出的异常,那么你应该让Lib2抛出与Lib1兼容的异常类型 - 意思是,在公共DLL中定义所有这样的异常类型,并且只抛出公共异常。 - dthorpe
没有花时间回答,但你们两个都是对的:我们不应该设计需要肮脏的黑客手段的东西。不幸的是,我不得不在现有的程序集上这样做,希望它没有进入生产环境。 - Guillaume

3
你应该在第一个应用程序域中加载Library2,或者你应该抛出一些在CommonLibrary中定义的异常。
附:引用类型的异常(在同一个应用程序域内)被按引用抛出,但是它们在不同的应用程序域之间被按值抛出(因为它们不是MarshalByRef的后代),你不能改变这个行为。请考虑:
//Oops! I can't do that!
public class MyException : Exception, MarshalByRef
{
}

附注:您可以使用序列化代理或类似的东西来解决您的问题,但我认为显式地抛出常见异常类型更加清晰和简单。


谢谢您的回答,但是我该如何使用序列化代理呢?我该如何注册它? - Guillaume
考虑 Jeffrey Richter 的文章“运行时序列化,第三部分”: http://msdn.microsoft.com/en-us/magazine/cc188950.aspx。但我并不确定这是否是一个好的解决方案。 - Sergey Teplyakov

-1
这是一个关于序列化绑定器的例子,正如您所要求的那样。这个程序是一个自定义的“序列化”程序,它的参数是一个“序列化绑定器”。
// ... 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;
    }
}

并且可以这样调用:

_foo.DeSerialize(@"C:\foo.dat", new BarBinder());

这里发生的情况是当实例化'BarBinder'并将其分配给BinaryFormatter的Binder属性时,由于序列化的数据具有类型名称Foo.SomeClass,我们应用了'BarBinder',将类型名称重命名为'FooBar.SomeClass',从而有效地使数据属于另一种类型...


1
谢谢,但BinarySerializer对于我的AppDomain问题不起作用。 - Guillaume

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接