2

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.

1
  • The AppDomain.Load method is loading the assembly in the current AppDomain which is not what you are looking for. You need a MarshalByRefDerived loader like the one in Michal's answer. Commented Dec 18, 2013 at 8:56

1 Answer 1

3

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.