views:

198

answers:

6

I've seen this problem come up a lot, but never adequately handled, and I haven't seen it on Stack Overflow, so here goes. I wish there were a way to put this shortly and succinctly without lacking clarity, but I can't seem to shorten it, so bear with me...

A good case-study (my current case, of course) to illustrate the problem follows:

I write code for many locations, a Parent Compary (parentco), and several satellite locations (centers). I have two 'Managers', one designed for the parentco, and one designed for the centers (deployed many times). I also have two libraries, one for the centers, and one generic library (that is used at the centers and the parentco), that programs can include to communicate to the appropriate Manager (via TCP). The library for the centers has several classes designed to wrap database tables and other 'Messages' to do other things, and the generic library has a few 'Messages,' too, such as 'end connection,' 'invoke a process,' and others.

The Question:

When the Manager recieves a Message that is defined in the 'generic' library, how can it know which type of message it is? The first-blush solution would be something like this:

namespace generic_library
{
    public interface IMessage_Creator
    {
        public IMessage Create_Message(short id);
    }
    public interface IMessage
    {
        short Message_ID { get; }
    }
    /// <summary>Perhaps a message to kill the current connection</summary>
    public class Generic_Message1 : IMessage
    {
        public short Message_ID { get { return ID; } }
        internal const short ID = 1;
    }
    public static class Message_Handler
    {
        private static readonly System.Collections.Generic.List<IMessage_Creator> _creators = 
            new System.Collections.Generic.List<IMessage_Creator>();
        public static void Add_Creator(IMessage_Creator creator)
        {
            _creators.Add(creator);
        }
        public static IMessage Get_Message(short id)
        {
            switch (id)
            {//the Generic library knows about the generic messages...
                case Generic_Message1.ID:
                    return new Generic_Message1();
            }
            //no generic message found, search the registered creators.
            IMessage ret = null;
            foreach (IMessage_Creator creator in _creators)
            {
                ret = creator.Create_Message(id);
                if (ret != null)
                {
                    return ret;
                }
            }
            //null if no creator was found.
            return ret;
        }
    }
}
namespace center
{
    public class Center_Creator : generic_library.IMessage_Creator
    {
        static Center_Creator()
        {
            generic_library.Message_Handler.Add_Creator(new Center_Creator());
        }
        public generic_library.IMessage Create_Message(short id)
        {
            switch (id)
            {//The center library knows about center-specific messages
                case center_message1.ID:
                    return new center_message1();
            }
            //we return null to say, "I don't know about that message id."
            return null;
        }
    }
    public class center_message1 : generic_library.IMessage
    {
        public short Message_ID
        {
            get { return ID; }
        }
        internal const short ID = 2;
    }
}

A little explanation. As you can see, the center and generic library have their own messages they can handle. The center interface (here represented by namespace center) registers his creator, Center_Creator, in the static constructor so when the Message_Handler gets a message of his type, the creator will be called on to generate the correct message.

The problem with this approach:

You may have already seen the problem here, and that is:

If the class Center_Creator is never accessed at all (one is never created, and a static method is never invoked) by code, which should be the case until a message of that type is recieved, the static constructor, static Center_Creator() is never invoked, so the Message_Handler never knows about this creator.

That's all fine and dandy, but I don't know how to fix it. Many people have suggested using reflection to invoke the Center_Creator Type Initializer, but I don't want to put that burden on every program that uses this library!

What is the Stack Overflow community's suggestion? Please let me know if I can simplify this to help make it more accessible for the community.

EDIT: The code is for the generic library and the Center Library. As you can see, I will have the same issues with the Parent Company library.

A diagram of the architecture. Image.

+4  A: 

Let's break this down:

  • You have an application which is to send & receive certain types of messages.
  • A message type must be registered before it can be read, however,
  • You do not register the type until you send a message, but
  • You want to be able to read a message before you write one.

Clearly the answer is that you are registering your message types at the wrong time.

I would suggest an explicitly called Init() method for message types. This could be done by using reflection to scan the libraries to see would types are defined, or by manually listing them.

James Curran
Maybe some IOC links?
GalacticCowboy
Perhaps I was unclear. I added a link to a picture of the problem at the bottom of the question. Let me know if you can't see it.So you're suggesting creating a static Init() Method on the Message_Creator classes, then using reflection to scan through all the classes, finding one with a static Init() method and invoking it (and it could then register itself)?I was hoping for a more natural solution, but this may work.
Limited Atonement
A: 

Some ideas:

  • Use .NET serialization to serialize/deserialize your messages and put them in a class library used by both ends (or even use WCF to handle communication).
  • Add a custom attribute to your creator classes and populate the creator list using reflection at the first time Get_Message is called ("if (!initialized) FindAndAddCreators();").
  • Introduce some initialization method in your library that registers all the creator classes.
Gnafoo
.Net serialization isn't the simple solution because I need interoperability with Java. Also, "put them in a class library used by both ends" is exactly what I don't want to do. Perhaps I wasn't clear in the original post, I want to keep the two libraries separate.Your point two, I think, depends on point three. To get the program to do the reflection thing and look for the necessary classes, I would need to ask everyone who uses the library to call the initialize method. I was hoping to avoid that burden from the plugins.
Limited Atonement
A: 

Try using a factory pattern.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace SO
{
    class Program
    {
        static void Main(string[] args)
        {
            MessageFactory factory = new MessageFactory();

            IMessage msg = factory.CreateObject(1);
            IMessage msg2 = factory.CreateObject(2);
        }
    }

    public interface IMessage
    {
        short Message_ID { get; }
    } 

    public class Generic_Message1 : IMessage 
    { 
        public short Message_ID { get { return ID; } } 
        internal const short ID = 1; 
    } 

    public class center_message1 : IMessage 
    { 
        public short Message_ID { get { return ID; } } 
        internal const short ID = 2; 
    } 

    public class MessageFactory
    {
        private Dictionary<short, Type> messageMap = new Dictionary<short, Type>();

        public MessageFactory()
        {
            Type[] messageTypes = Assembly.GetAssembly(typeof(IMessage)).GetTypes();
            foreach (Type messageType in messageTypes)
            {
                if (!typeof(IMessage).IsAssignableFrom(messageType) || messageType == typeof(IMessage))
                {
                    // messageType is not derived from IMessage   
                    continue;
                }

                IMessage message = (IMessage)Activator.CreateInstance(messageType);               
                messageMap.Add(message.Message_ID, messageType);
            }
        }

        public IMessage CreateObject(short Message_ID, params object[] args)
        {
            return (IMessage)Activator.CreateInstance(messageMap[Message_ID], args);
        }
    } 

}

EDIT to answer comment:

If the "generic" library is the one processing the messages, and it has no knowledge of the types of message is is processing, you obviously have to change that.

Either move to a "plug-in" model where your custom message dlls will be loaded from a specific directory on startup of the generic library, or read the custom message dlls from a config file at startup for the generic library.

// Read customMessageDllName and customMessageClassName from your config file 
Assembly assembly = Assembly.Load(customMessageDllName); 
IMessage customMessage = (IMessage)assembly.CreateInstance(customMessageClassName); 
GalacticJello
This doesn't address the problem. I have the factory under control. The problem is putting these in two separate libraries, Generic not knowing about Center. Thanks for the suggestion.
Limited Atonement
Actually, it directly addresses your problem. As long as the IMessage implementations exist in the Application Domain, they will be instantiated without you having to call an Init on them.
GalacticJello
My first assessment was too quick. Your answer is better than I thought (upvote). I can't quite get it to work, though. The type IMessage is defined in the 'generic' assembly, the executing assembly when this needs to take place is the 'generic' assembly, the entry assembly is the program that gets and sends messages, the calling assembly is the 'generic' assembly. In short, I don't have a reference to the 'center' assembly. Any suggestions?
Limited Atonement
"If the `generic' library is the one processing the messages, and it has no knowledge of the types ... you obviously have to change that."I've added an answer that corrects you, although, at a cost.
Limited Atonement
+1  A: 

Hi,

your message handlers can be seen as plugins which makes your problem a potential fit for the Managed Extensibility Framework. Since .Net 4 it's also shipped with the .Net framework.

You can find sample introductions to MEF here and here.

I've put together a litte example to show that it's quite simple to use basic MEF functionality (although there is much more you can do with it). First there is a PluginHost class which will host the plugins in its Plugins collection. Then there's a simple interface containing just the property Description and an example implementation of a plugin called ExamplePlugin.

The Plugins collection will be filled by the container.ComposeParts(..) method called in the constructor. All that's required to make that magic happen are the [Export] and [ImportMany] attributes.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace Playground
{
    public class Program
    {
        static void Main(string[] args)
        {
            PluginHost host = new PluginHost();
            host.PrintListOfPlugins();
            Console.ReadKey();
        }
    }

    public class PluginHost
    {
        [ImportMany]
        public IEnumerable<IPlugin> Plugins { get; set; }

        public PluginHost()
        {
            var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);
        }

        public void PrintListOfPlugins()
        {
            foreach (IPlugin plugin in Plugins)
                Console.WriteLine(plugin.Description);
        }
    }

    public interface IPlugin
    {
        string Description { get; }
    }

    [Export(typeof(IPlugin))]
    public class ExamplePlugin : IPlugin
    {
        #region IPlugin Members

        public string Description
        {
            get { return "I'm an example plugin!"; }
        }

        #endregion
    }
}

UPDATE: You can use so called Catalogs to discover plugins in more than one assembly. For example there is a DirectoryCatalog which gives you all exports found in all assemblies in a given directory.

AppDomain.CurrentDomain.GetAssemblies(); returns an array of all assemblies loaded into the current AppDomain. You could then iterate over that array to create an AggregateCatalog containing an AssemblyCatalog per loaded assembly.

andyp
I guess I'll have to look at it, but I was hoping not to have to learn another framework.
Limited Atonement
It's actually not very hard. Essentially you have to add some attributes to your methods and call a framework method to resolve the exports.
andyp
So basically, it looks like this approach uses Reflection in coordination with Attributes to register the plugins. Of course, there are lots of bells and whistles that are added by using the MEF, but all I'm wanting is an automated way to register the plugins without putting burden on the developer. Thanks for the example.
Limited Atonement
Yes, exactly! And hm, isn't that just what MEF offers?
andyp
Wouldn't he be in the same position because the "control" class doesn't exist in the "generic" assembly (and is not loaded)?
GalacticJello
I assumed that maybe the MEF would be smarter and would be able to see all assemblies loaded by the current process...?? If I could find out how to do that, I would be able to at least make an ugly solution.
Limited Atonement
I updated my answer with a way to discover all plugins in all currently loaded assemblies.
andyp
+1 for the Catalog functionality... nice.
GalacticJello
A: 

Why not simply use WCF? You'll get ease of development, great support, as well as interoperability with Java.

John Saunders
A: 

Gallactic Jello is on the right path. The part he left out is overcoming the problem of the generic library knowing about classes in the center library, which I have further addressed. I've created a sample solution with three projects, the full contents of which I'll spare you. Here is the gist.

Class Library: Generic lib

Contains a Message_Handler, his own IMessage_Creator, definitions of the interfaces, and an IMessage type of his own.

Class Library: Center Lib

Contains an IMessage_Creator, and his own IMessage type.

Application: Application

has a SVM (static void Main()) containing the following lines of code:

Generic_lib.IMessage msg = Generic_lib.Message_Handler.get_message(2); //a Center Message
if (msg is Center_lib.Center_Message)
{
    System.Console.WriteLine("got center message");
}

You will be amazed how important the if statement is!!! I'll explain later

Here's the code in the Type Initializer for Generic_lib.Message_Handler:

static Message_Handler()
{
    //here, do the registration.
    int registered = 0;
    System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
    foreach (System.Reflection.Assembly asm in assemblies)
    {
        System.Type[] types = asm.GetTypes();
        foreach (System.Type t in types)
        {
            System.Type[] interfaces = t.GetInterfaces();
            foreach (System.Type i in interfaces)
            {
                if (i == typeof(IMessage_Creator))
                {
                    System.Reflection.ConstructorInfo[] constructors = t.GetConstructors();
                    foreach (System.Reflection.ConstructorInfo ctor in constructors)
                    {
                        if (ctor.GetParameters().Length == 0)
                        {
                            Add_Creator(ctor.Invoke(new object[0]) as IMessage_Creator);
                            registered++;
                        }
                    }
                }
            }
        }
    }
    System.Diagnostics.Debug.WriteLine("registered " + registered.ToString() + " message creators.");
}

Horrific, isn't it? First, we get all the assemblies in the current domain, and here's where the if statement comes in. If there was no reference to the 'Center__lib' anywhere in the program, the array of Assemblies won't contain Center_lib. You need to be sure that your reference to it is good. Creating a method that is never called that references it is not enough, a using statement is not good enough,

if (msg is Center_lib.Center_Message) ;

is not enough. It has to be a reference that can't be optimized away. The above are all optimized away (even in Debug mode, specifying `don't optimize.'

I hope someone can come up with an even more elegant solution, but this will have to do for now.

Aaron

Limited Atonement
The problem here (with the if statement) is more subtle than I thought. The current assembly (process? domain?) must have loaded a METHOD that references the needed assemblies. So, if the needed reference is in a method that hasn't been run yet, it isn't in the list of CurrentDomain.GetAssemblies() ! Still investigating.
Limited Atonement
See my answer edit... load your message assemblies when you load "generic".
GalacticJello
Also, using @andyp's MEF with the "Catalog" functionality would accomplish the same thing.
GalacticJello