Question

I have an example application that has a number of endpoints (c# classes) which use an interface which defines some methods. These endpoints are in their own class libraries.

In an assembly called "EndPoints"

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

In an assembly called "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");
        }
    }
}

The application will "choose" an endpoint to use and then find the appropriate class, create an instance of it and then call the methods in turn.

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

I want to change the application to achieve the following;
- each endpoint assembly (and any config files and/or other assemblies that it uses) are contained in a subfolder below the application folder. the subfolder name would be the name of the endpoint class e.g. "EndPoint1" - each endpoint runs in its own appdomain.

However, so far I've been unable to achieve this. I keep getting an exception stating its failed to load the appropriate assembly even though when I create the appdomain I specify the subfolder to be used by setting the ApplicationBase and PrivateBinPath properties of the AppDomainSetup; 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);

I've then been trying to use the Load method on the newly created appDomain to load the assembly. That's when I get the error.

Please does anyone have any thoughts about how I can load the appropriate assembly and call the methods defined in the interface? Many thanks.

Was it helpful?

Solution

I would do it in the following way. Firstly you need a class derived from MarshalByRef. It will be responsible for loading EndPoints and executing them in separate application domains. Here, I assume that it is defined in ConsoleApplication1 but it can be moved somewhere else:

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++)
        {
             ....
        }
    }
} 

Here is a code that uses this class. You can put in in your LoadEndPointAssembly method.

var appDomain = AppDomain.CreateDomain(endPointName);
var loader = (EndPointLoader)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(EndPointLoader).FullName);
loader.Load(assemblyPath, endPointName);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top