views:

316

answers:

2

I asked this on the MEF Codeplex forum already, but I haven't gotten a response yet, so I figured I'd try StackOverflow. Here's the original post if anyone's interested (this is just a copy from it):

MEF Codeplex

"Let me first say that I'm completely new to MEF (just discovered it today) and am very happy with it so far. However, I've ran in to a problem that is very frustrating. I'm creating an app that will have a plugin architecture and the plugins will only be stored in a single DLL file (or coded into the main app). The DLL file needs to be able to be recompiled during run-time and the app should recognize this and re-load the plugins (I know this is difficult, but it's a requirement). To accomplish this I took the approach covered http://blog.maartenballiauw.be/category/MEF.aspx there (look for WebServerDirectoryCatalog). Basically the idea is to "monitor the plugins folder, copy the new/modified assemblies to the web application’s /bin folder and instruct MEF to load its exports from there." This is my code, which is probably not the correct way to do it but it's what I found in some samples around the net:

        main()...
    string myExecName = Assembly.GetExecutingAssembly().Location;
        string myPath = System.IO.Path.GetDirectoryName(myExecName);
        catalog = new AggregateCatalog();
        pluginCatalog = new MyDirectoryCatalog(myPath + @"/Plugins");
        catalog.Catalogs.Add(pluginCatalog);


        exportContainer = new CompositionContainer(catalog);

        CompositionBatch compBatch = new CompositionBatch();
        compBatch.AddPart(this);
        compBatch.AddPart(catalog);
        exportContainer.Compose(compBatch);

and

    private FileSystemWatcher fileSystemWatcher;
    public DirectoryCatalog directoryCatalog;
    private string path;
    private string extension;

    public MyDirectoryCatalog(string path)
    {
        Initialize(path, "*.dll", "*.dll");
    }

    private void Initialize(string path, string extension, string modulePattern)
    {
        this.path = path;
        this.extension = extension;
        fileSystemWatcher = new FileSystemWatcher(path, modulePattern);
        fileSystemWatcher.Changed += new FileSystemEventHandler(fileSystemWatcher_Changed);
        fileSystemWatcher.Created += new FileSystemEventHandler(fileSystemWatcher_Created);
        fileSystemWatcher.Deleted += new FileSystemEventHandler(fileSystemWatcher_Deleted);
        fileSystemWatcher.Renamed += new RenamedEventHandler(fileSystemWatcher_Renamed);
        fileSystemWatcher.IncludeSubdirectories = false;
        fileSystemWatcher.EnableRaisingEvents = true;
        Refresh();
    }
    void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
    {
        RemoveFromBin(e.OldName);
        Refresh();
    }
    void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
    {
        RemoveFromBin(e.Name);
        Refresh();
    }
    void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
    {
        Refresh();
    }
    void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
    {
        Refresh();
    }
    private void Refresh()
    {
        // Determine /bin path 
        string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
        string newPath = "";
        // Copy files to /bin 
        foreach (string file in Directory.GetFiles(path, extension, SearchOption.TopDirectoryOnly))
        {
            try
            {
                DirectoryInfo dInfo = new DirectoryInfo(binPath);
                DirectoryInfo[] dirs = dInfo.GetDirectories();
                int count = dirs.Count() + 1;
                newPath = binPath + "/" + count;
                DirectoryInfo dInfo2 = new DirectoryInfo(newPath);
                if (!dInfo2.Exists)
                    dInfo2.Create();

                File.Copy(file, System.IO.Path.Combine(newPath, System.IO.Path.GetFileName(file)), true);
            }
            catch
            {
                // Not that big deal... Blog readers will probably kill me for this bit of code :-) 
            }
        }
        // Create new directory catalog 
        directoryCatalog = new DirectoryCatalog(newPath, extension);
        directoryCatalog.Refresh();
    }
    public override IQueryable<ComposablePartDefinition> Parts
    {
        get { return directoryCatalog.Parts; }
    }
    private void RemoveFromBin(string name)
    {
        string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "");
        File.Delete(Path.Combine(binPath, name));
    }

So all this actually works, and after the end of the code in main my IEnumerable variable is actually filled with all the plugins in the DLL (which if you follow the code is located in Plugins/1 so that I can modify the dll in the plugins folder). So now at this point I should be able to re-compile the plugins DLL, drop it in to the Plugins folder, my FileWatcher detect that it's changed, and then copy it into folder "2" and directoryCatalog should point to the new folder. All this actually works! The problem is, even though it seems like every thing is pointed to the right place, my IEnumerable variable is never updated with the new plugins. So close, but yet so far! Any suggestions? I know the downsides of doing it this way, that no dll is actually getting unloaded and causing a memory leak, but it's a Windows App and will probably be started at least once a day, and the plugins are un-likely to change that often, but it's still a requirement from the client that it does this without re-loading the app. Thanks!

Thanks for any help you all can provide, it's driving me crazy not being able to figure this out."

+3  A: 

There is no trigger for recomposition, because your catalog implementation doesn't provide notifications. Implement INotifyComposablePartCatalogChanged to fix this.

Wim Coenen
Thanks for your reply, but I don't see how that helps me. After playing around with MEF some more, it seems like I could just change my catalog implementation to basically a storage class and get rid of the ComposablePartCatalog, since I already have a directoryCatalog in there. DirectoryCatalog does implement INotifyComposablePartCatalogChanged, which it would seem to me means it should be updating things properly if I changed my code in main to catalog.Catalogs.Add(pluginCatalog.directoryCatalog);.
@mybrokengnome: the MEF container won't magically notice if you change `pluginCatalog.directoryCatalog` to a new one. It will still be listening to change notifications of the old one. Seems to me change notification is very relevant to your question; how could it not be?
Wim Coenen
I'm not changing it after the case, it now always points to pluginCatalog.directoryCatalog (a real directory catalog, not a catalog I created). Since DC implements INotifyComposablePartCatalogChanged I'm confused as to why the DC is not discovering the new files, even after I create a new DC with a new path. I get that change notification is relevant but A) With my current implementation (that i just described) then change notification is implemented via the MEF DC and B) Is there any examples somewhere of what to do to implement you're own notification listeners that would work like this?
A: 

I believe MEF can only load one version of the same assembly (I was trying on Silverlight though)

Clems