This project has moved and is read-only. For the latest updates, please go here.

Plug-in not updating

Apr 10, 2010 at 11:37 PM

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 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:


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

            exportContainer = new CompositionContainer(catalog);

            CompositionBatch compBatch = new CompositionBatch();



 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;
        void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
        void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        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))
                    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)

                    File.Copy(file, System.IO.Path.Combine(newPath, System.IO.Path.GetFileName(file)), true);
                    // Not that big deal... Blog readers will probably kill me for this bit of code :-) 
            // Create new directory catalog 
            directoryCatalog = new DirectoryCatalog(newPath, extension);
        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! 
Apr 12, 2010 at 6:19 PM
Just wanted to add that I do have my IENumerable property attribute set to AllowRecomposition: [ImportMany(AllowRecomposition=true)] public IEnumerable<ModelInterface> _myPlugins { get; set; } If anyone can provide insight I'd appreciate it, it's very frustrating that it seems like everything is set properly, but _myPlugins never gets updated with the new information.
Apr 12, 2010 at 11:48 PM
Edited Apr 12, 2010 at 11:48 PM
Link back to the stackoverflow cross-post where I answered:
Apr 13, 2010 at 4:16 AM
I updated the Stackoverflow thread with this too, but I wanted to post it here in case someone had some more insight. Please remember I'm new to MEF, and most of the samples and tutorials I can find seem to be out of date (I'm using preview 9). "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);" The rest of my code in MyDirectoryCatalog is the same, I'm still creating the DC via directoryCatalog = new DirectoryCatalog(newPath, extension);, and since it's implementing the INotifyComposablePartCatalogChanged shouldn't it be providing the updates to the correct places?
Apr 13, 2010 at 3:29 PM

Yes as mentioned on stackoverflow the issue is that your custom catalog does not implement INotifyComposablePartCatalogChanged. I'm not sure I entirely understand what this directory catalog is doing beyond listening to changes on the disk and updating accordingly. It appears to be creating/deleting directories of which I"m not sure I understand why exactly. 

The reason why this still isn't updating like you want is because the DirectoryCatalog you are creating is not the catalog directly in the container it is your custom catalog that is in the container, also currently you are completely throwing away the old directory catalog when you get a change and replacing it with the new one which knows nothing about the old one so it couldn't fire events if it wanted to.

It sounds to me that you could get away with on of two approaches:
1) Make your MyDirectoryCatalog inherit from DirectoryCatalog directly and only calling Refresh when the filesystemwatcher notifications come in
2) Make your MyDirectoryCatalog not a catalog any longer but a simple FileSystemWatcher helper that will take a DirectoryCatalog (which you construct outside and directly add to the AggregateCatalog) and will simply call Refresh on that DirectoryCatalog when any notifications come in.

With all that said you should be very careful about using the FileSystemWatcher for real applications because it has some potential issues. We originally had that functionality in our DirectoryCatalog but decided to remove it because it added to much unpredictability. For example if you copy two assemblies into the plugins directory, assembly A and assembly B, such that A has a dependency on B, the file watcher could very easily give you a notification for A before B is copied and if you try to load A at that time it will fail. 


Apr 13, 2010 at 3:40 PM
Edited Apr 13, 2010 at 7:23 PM
Wes, That all makes sense. Thanks. The reasoning behind why I'm doing it the way I am is because if I create a DirectoryCatalog and give it a path(required) that points to bin/Plugins and in there I have my 1 plugin, on startup it gets locked down and can't be replaced, which as I mentioned one requirement of this all is that the DLL has to be replaced during runtime. So, to get around this, the idea was to take the DLL that's in the Plugin folder at startup, copy it to a new directory and then point the DirectoryCatalog to this new directory (Shadow Copy?). This gives me the newest version of the DLL without locking down the one we want to replace, and once its replaced it will copy this to a new directory, create a new DirectoryCatalog with the new path. Since there's no way to change the path/fullpath in a DC since it's private(without using reflection, but that's just bad technique), I was creating a new one every time. The more I read, and the more you guys tell me, the less likely I see that this all is actually possible with MEF right now (at least given my skill level with it, or maybe not at all). Even with your methods, it can refresh all it wants, but the problem I see is that it's still just pointing to an old directory that I no longer care about, and both methods require me to pass in a path that will eventually get locked down and I'm not able to replace.
Apr 13, 2010 at 7:22 PM
I just wanted to add, maybe this is what's causing some confusion, is that there is only 1 DLL ever (with a bunch of plugins in it though). There's not going to be 'PLugin 1" "Plugin 2". It's going to be Plugin 1 all the time, except they can change something in the code for plugin 1 and then they need the ability to drop the new one over top of the old one. So yes, recompoisition does work if I drop in 2 files (same file, different names), I will get N * 2 items loaded into my array (N being the number of exports in the DLL), but it's not working the way I'd like it to currently. I hope between this post and my previous it sheds some light on to why I'm trying to do it this way, and also why nothing I've tried has worked so far.
Apr 13, 2010 at 7:41 PM

Actually if I understand what you are trying to do I don't even believe it is supported by the CLR loader. If you try to load to assemblies with the same identity in the same process it will only load the first one and on the second time it will simply return you the assembly it already loaded (i.e. so you won't get any of the changes). The only time that would not be the case is if you have strongly signed your assemblies and updated the version number (if you simply update the version number without signing then they will appear to have the same identity). 

So what I'm getting at is this strategy would only work if all your extension authors actually properly signed and versioned their assemblies.

With all that said unless application really needs this feature I would shy away from it, especially if it is only a nice-to-have because it can be very complicated and more work than it worth.