Search code examples
c#mef

Modular Program. Calling Functions In The Host


I have built a modular program using

http://www.codeproject.com/Articles/258681/Windows-Forms-Modular-App-using-MEF as a base and I have several of my modules working.

It is a MDI Windows Forms application and I need to call back to the host module for some stuff.

a) Location information for the MDI host window

b) Write to the status bar in the host window.

I have managed to get the application to compile but when I call the host functions it always gives me a null exception

I did look at Using MEF with C#, how do I call methods on the host, from the plugin?

which is where I got my line

public Exec.Core.Interfaces.IHost Host;

But host is always null so I get exceptions trying to access the members of MDIForm which is the host.

Even if I do public Exec.Core.Interfaces.IHost Host {get;set;}

This is the Host.

NameSpace Exec
{
  [Export(typeof(Exec.Core.Interfaces.IHost))]
   //  MDIForm is the host.    
  public partial class MDIForm : Form, Exec.Core.Interfaces.IHost
  {
     ///other stuff not related to the problem


      // defined in public interface IHost
    public Point myLocation()
    {
      return this.Location;  // need the window location

    }
      // defined in public interface IHost
    public IHost GetHost()
    {  // is this what GetHost Should Return? Not sure
      return this;
    }
      // defined in public interface IHost
    public void SendMessage(string message)
    {
      SetStatusBar(message); // print a message to MDIForm status bar
    }
  }
}

Then is the IHosts.cs

namespace Exec.Core.Interfaces
{
    public interface IHost
    {
        IHost GetHost();
        void SendMessage(string message);
        Point myLocation();
       // MDIForm GetThis( );  /* this gives error. Can't resolve MDIForm
                                  I don't know why and can't resolve.*/
    }
}

This is one of the modules where I am trying to get stuff from the host

namespace Exec.Modules.Tasks
{
   [Export]
   public partial class frmTasks : Form
   {
       [Import(typeof (Exec.Core.Interfaces.IHost))] 
       public Exec.Core.Interfaces.IHost Host;
           // unfortunately Host == NULL at this point

       private void SendMessage (string message)
       {
           try
           {
              Host.SendMessage(message);  <Throws System.NullReferenceException
           }
           catch (Exception ex)
           {
              MessageBox.Show(ex.ToString());
           }
       }
       private IHost WhichHost()
       {
           try
           {     /// not really sure what will be returned here
               return GetHost();<Throws System.NullReferenceException
           }
           catch (Exception ex)
           {
               MessageBox.Show(ex.ToString());

            }
       }
       private Point Location()
       {
          try
          {
             return mylocation(); <Throws  System.NullReferenceException
          }
          catch (Exception ex)
          {
              MessageBox.Show(ex.ToString());
          }
       }
   }
}

And finally this is how I put all of the objects together in ModuleHandler.cs This is pretty much taken from the codeproject above with some seperation of some method calls into 2 pieces so I could see why it was dying.

 namespace Exec.Core   
 {
     [Export(typeof(IModuleHandler))]
     public class ModuleHandler : IDisposable, IModuleHandler
     {

        [ImportMany(typeof(IModule), AllowRecomposition = true)]
         // The ModuleList will be filled with the imported modules
        public List<Lazy<IModule, IModuleAttribute>> ModuleList
        { get; set; }

        [ImportMany(typeof(IMenu), AllowRecomposition = true)]
        // The MenuList will be filled with the imported Menus
        public List<Lazy<IMenu, IModuleAttribute>> MenuList { get; set; }

        [Import(typeof(IHost))]
        // The imported host form
        public IHost Host { get; set; }


       AggregateCatalog catalog = new AggregateCatalog();
       public void InitializeModules()
       {
          // Create a new instance of ModuleList
          ModuleList = new List<Lazy<IModule, IModuleAttribute>>();
          // Create a new instance of MenuList
          MenuList = new List<Lazy<IMenu, IModuleAttribute>>();

          // Foreach path in the main app App.Config      
          foreach (var s in ConfigurationManager.AppSettings.AllKeys)
          {
            if (s.StartsWith("Path"))
            {
             // Create a new DirectoryCatalog with the path loaded from the App.Config
                DirectoryCatalog cataloglist = new DirectoryCatalog(ConfigurationManager.AppSettings[s], "jobexe*.dll");
               catalog.Catalogs.Add(cataloglist);
             }
           }
            // Create a new catalog from the main app, to get the Host
           catalog.Catalogs.Add( new AssemblyCatalog(System.Reflection.Assembly.GetCallingAssembly()));
                    // Create a new catalog from the ModularWinApp.Core
            DirectoryCatalog catalogExecAssembly = new DirectoryCatalog( System.IO.Path.GetDirectoryName(
              System.Reflection.Assembly.GetExecutingAssembly().Location ), "exe*.dll");

            catalog.Catalogs.Add(catalogExecAssembly);
            // Create the CompositionContainer
            CompositionContainer cc = new CompositionContainer(catalog);

            try
            {
                cc.ComposeParts(this);
            }
            catch (ReflectionTypeLoadException e)
            { MessageBox.Show(e.ToString()); }

            catch (ChangeRejectedException e)
            { MessageBox.Show(e.ToString()); }     
        }            
    }
}

So again, the modules work independently but are unable to call back to the host. Wondering what I am doing wrong.

Thanks in advance for any help

One final thing that may have something to do with the issue.

Here is the code that starts the program

 public static ModuleHandler _modHandler = new ModuleHandler();

static void Main()
{
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

  //Initialize the modules. Now the modules will be loaded.
   _modHandler.InitializeModules();
    // this goes straight to class MDIForm()  constructor
  Application.Run(_modHandler.Host as Form);

}

Colin


Solution

  • You instantiate your ModuleHandler manually and then call InitializeModules, where a catalog is created and passed to a new composition container. Then, this container is used to satisfy all the imports of that particular ModuleHandler instance through the line:

    cc.ComposeParts(this);
    

    This tells MEF to look for Import attributes and populate the decorated properties with instances of classes decorated with the corresponding Export attributes.

    What you are missing is the analogous call to populate your frmTasks objects. Thus, the following Import is not satisfied and the property is null:

    [Import(typeof (Exec.Core.Interfaces.IHost))] 
    public Exec.Core.Interfaces.IHost Host;
    

    You have several options among which I'd look into the following two:

    • Modify the IModule interface so that you can explicitly pass an IHost to the modules. Then, in the InitializeModules, after the call to ComposeParts, iterate over the composed modules passing them the host instance. This accounts for setting the Host property through the IModule interface. You could also stick to the MEF imported property by putting it in the IModule interface and calling ComposeParts for each module instance.

    • Expose the container through a ServiceLocator and get the IModuleHandler instance from the modules to access the Host property.