MEF and ShadowCopying DLLs so that I can overwrite them at runtime
Asked Answered
G

3

12

I am trying to stop my application locking DLLs in my MEF plugin directory so that I can overwrite the assemblies at runtime (note I'm not actually trying to have MEF reload them on the fly, at the next app start is fine, i just dont want to have to stop the app to do the copy)

I am trying to do this by creating a shadow copied app domain for my mef loaded assemblies as below:

[Serializable]
    public class Composer:IComposer
    {
        private readonly string _pluginPath;
        public Composer(IConfigurePluginDirectory pluginDirectoryConfig)
        {
            _pluginPath = pluginDirectoryConfig.Path;
            var setup = new AppDomainSetup();
            setup.ShadowCopyFiles = "true"; // really??? is bool not good enough for you?
            var appDomain = AppDomain.CreateDomain(AppDomain.CurrentDomain.FriendlyName + "_PluginDomain", AppDomain.CurrentDomain.Evidence, setup);

            appDomain.DoCallBack(new CrossAppDomainDelegate(DoWorkInShadowCopiedDomain));      
        }

        private void DoWorkInShadowCopiedDomain()
        {
            // This work will happen in the shadow copied AppDomain.

            var catalog = new AggregateCatalog();
            var dc = new DirectoryCatalog(_pluginPath);
            catalog.Catalogs.Add(dc);
            Container = new CompositionContainer(catalog);
        }

        public CompositionContainer Container { get; private set; }
    }

and then access my MEF component catalog via the CompositionContainer on this class. However the composition container seems to only be initialised inside the shadowcopy domain (which makes sense) and this means that its null in my application domain. I was just wondering if theres a better way to do this or some way to cross domain query to get my MEF components

Gastronome answered 26/9, 2012 at 1:22 Comment(3)
It seems like you could simply call File.Copy on all the files in the plugin directory, using your own manually created shadow directory. Then you just load from the shadow path in your main domain.Cerate
I thought about this but my understanding is that this is the problem shadow copying is supposed to solve (so they probably have more edge cases covered to do with this). Do you know if shadowcopying is larger than just this or is doing the copy manually good enough?Gastronome
@DanBryant based on the answers I've gotten I feel that this is the best solution for us, if you post it as an answer before the bounty ends I will award you the bounty.Gastronome
D
3

If you don't want to follow the solution from Dan Bryant and zync, you could create a shell application that simply executes your application in a new AppDomain.

An approach would be:

  1. Create a new application project which will be the shell application.
  2. In the shell application, create the AppDomain, enable shadow copying and if you wish, specify the directory where shadow copying will be enabled.
  3. Use AppDomain.ExecuteAssembly to call your current application.

If instead of an application you have a class library you can try the following:

  1. Create a new class library project.
  2. Add the following interface to the new class library project:

    public interface IRemoteLoader  
    {  
        void Load();  
        void Unload();  
    }
    
  3. Add an implementation of this interface to your class library that needs to execute in a new AppDomain. In the Load and Unload methods you should add code to perform initialization and cleanup respectively. Make the class derive from MarshalByRefObject. This is needed for .NET Remoting to create proxy objects on both AppDomains.

  4. After you create the new AppDomain, use CreateInstanceAndUnwrap to create an instance of the loader class from step 3.

  5. Use the Load and Unload on the object created from step 4.

This will be enough if you do not fine-grained control and simply starting/stopping is enough.

Decedent answered 4/10, 2012 at 14:19 Comment(2)
@panosrontogainnis yeah I agree this seems like the most supported way of solving the problem (ie bootstrap/shadowcopy the whole application. Unfortunately in my case I don't control the bootstrap (as I'm in NServiceBus.Hosted) so this solution wont work for me specifically. Having said that I feel its probably the best solution for others.Gastronome
Cheers. I added another approach that works with class libraries.Decedent
L
-1

This scenario is closer to the auto update feature in mobile apps. Essentially you want to pick up new assemblies if available on App Start / Restart.

One way to design this might be to have a communication mechanism to signal to your app on start that new assemblies are available (maybe a version.txt file). If yes, then the same version.txt file could point to the new location of the assemblies. Yes - you may end up creating a number of sub folders to point to the correct version, but these could be cleaned up by another process.

you could use a hierarchichal structure like so -

Version\ - Version1.0\ - Version2.0\

This type of design would be closer to a well know paradigm of auto update.

Looney answered 1/10, 2012 at 14:11 Comment(2)
addeed this as a new answer because its a different approach to solve the overall problem rather than the specific issue.Looney
i think the approach you are suggesting is the same as Dan Bryants earlier suggestion, to manually perform the same function as shadow copy. I feel a little hacky doing this as its basically cloning an existing .NET function. I just want to make sure before I do this that there isn't a good way of getting around the cross domain access.Gastronome
L
-2

Do you have the option of not using a DirectoryCatalog and use AssemblyCatalog to load up all the assemblies in the directory ? You could even go to code plex and copy the same code from the DirectoryCatalog class that reads through the directory and loads up the assemblies.

You would lose the ability to load them on the fly, but as you mentioned that's not really a requirement.

Looney answered 29/9, 2012 at 20:16 Comment(3)
Hey @zync perhaps I'm missing what you re meaning but how do I get around the shadow copy/cross app-domain issue by implementing my own DirecoryCatalog. Is there a way to tell it to pull from a separate app domain? perhaps you could add a few more details on what you meanGastronome
@LukeMcGregor - my suggestion won't work. But i did some digging around and this isn't specifically a MEF issue. The appdomain is locking any touched assemblies. https://mcmap.net/q/1012990/-overwriting-dlls-in-mefLooney
yeah you're right my issue isnt specifically a MEF issue its more a shadow copy/appdomain issue.Gastronome

© 2022 - 2024 — McMap. All rights reserved.