创建AppDomain并从子文件夹中的程序集调用方法

时间:2013-12-17 11:36:51

标签: c# .net .net-assembly appdomain

我有一个示例应用程序,它有许多端点(c#类),它们使用定义某些方法的接口。这些端点位于各自的类库中。

在名为“EndPoints”的程序集中

namespace EndPoints
{
    public interface IEndPoint
    {
        void Initialize(XmlDocument message);
        bool Validate();
        void Execute();
    }
}

在名为“EndPoints.EndPoint1”的程序集中

namespace EndPoints
{
    public class EndPoint1 : IEndPoint
    {
        private XmlDocument _message;

        public void Initialize(XmlDocument message)
        {
            _message = message;
            Console.WriteLine("Initialize EndPoint1");
        }

        public bool Validate()
        {
            Console.WriteLine("Validate EndPoint1");
            return true;
        }

        public void Execute()
        {
            Console.WriteLine("Execute EndPoint1");
        }
    }
}

应用程序将“选择”要使用的端点,然后找到相应的类,创建它的实例,然后依次调用这些方法。

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Generate "random" endpoint name
            string endPointName = GetEndPointName();

            // Get the class name from the namespaced class
            string className = GetClassName(endPointName);

            // Dummy xmldocument that used to pass into the end point
            XmlDocument dummyXmlDocument = new XmlDocument();

            // Load appropriate endpoint assembly because the application has no reference to it so the assembly would not have been loaded yet
            LoadEndPointAssembly(endPointName);

            // search currently loaded assemblies for that class
            var classTypes = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => p.FullName == endPointName)
                .ToList();

            // cycle through any found types (should be 1 only)
            for (int i = 0; i < classTypes.Count; i++)
            {
                var classType = classTypes[i];
                IEndPoint classInstance = Activator.CreateInstance(classType) as IEndPoint;

                classInstance.Initialize(dummyXmlDocument);

                if (classInstance.Validate())
                {
                    classInstance.Execute();
                }
            }
        }

        private static void LoadEndPointAssembly(string endPointName)
        {
            using (StreamReader reader = new StreamReader(endPointName + ".dll", System.Text.Encoding.GetEncoding(1252), false))
            {
                byte[] b = new byte[reader.BaseStream.Length];

                reader.BaseStream.Read(b, 0, System.Convert.ToInt32(reader.BaseStream.Length));
                reader.Close();

                AppDomain.CurrentDomain.Load(b);
            }
        }

        private static string GetEndPointName()
        {
            // Code to create "random" endpoint class name
            Random rand = new Random();
            int randomEndPoint = rand.Next(1, 4);
            return string.Format("EndPoints.EndPoint{0}", randomEndPoint);
        }

        private static string GetClassName(string namespacedClassName)
        {
            string className = null;
            string[] components = namespacedClassName.Split('.');

            if (components.Length > 0)
            {
                className = components[components.Length - 1];
            }

            return className;
        }
    }
}

我想更改应用程序以实现以下目标;
- 每个端点程序集(以及它使用的任何配置文件和/或其他程序集)都包含在应用程序文件夹下的子文件夹中。子文件夹名称将是端点类的名称,例如“端点1” - 每个端点都在自己的appdomain中运行。

然而,到目前为止,我一直无法做到这一点。我一直得到一个异常,说明它无法加载适当的程序集,即使在创建appdomain时我通过设置AppDomainSetup的ApplicationBase和PrivateBinPath属性来指定要使用的子文件夹; e.g。

AppDomain appDomain = null;
AppDomain root = AppDomain.CurrentDomain;
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = root.SetupInformation.ApplicationBase + className + @"\";
setup.PrivateBinPath = root.SetupInformation.ApplicationBase + className + @"\";

appDomain = AppDomain.CreateDomain(className, null, setup);

然后我一直在尝试在新创建的appDomain上使用Load方法来加载程序集。那是我收到错误的时候。

请问有关如何加载相应的程序集并调用界面中定义的方法的任何人都有任何想法吗?非常感谢。

1 个答案:

答案 0 :(得分:2)

我会按以下方式进行。首先,您需要一个派生自 MarshalByRef 的类。它将负责加载 EndPoints 并在单独的应用程序域中执行它们。在这里,我假设它是在 ConsoleApplication1 中定义的,但它可以移动到其他地方:

public class EndPointLoader : MarshalByRefObject
{
    public void Load(string path, string endPointName)
    {
        Assembly.LoadFrom(path);

        var classTypes = AppDomain.CurrentDomain.GetAssemblies()
                                  .SelectMany(s => s.GetTypes())
                                  .Where(p => p.FullName == endPointName)
                                  .ToList();

        for (int i = 0; i < classTypes.Count; i++)
        {
             ....
        }
    }
} 

以下是使用此类的代码。您可以使用 LoadEndPointAssembly 方法。

var appDomain = AppDomain.CreateDomain(endPointName);
var loader = (EndPointLoader)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(EndPointLoader).FullName);
loader.Load(assemblyPath, endPointName);