MEF and AppDomain - Remove Assemblies On The Fly

This article will give an idea of what's involved in interacting with a running MEF based application and how to either remove a Composition part or replace it with a new version, without having to shut down your application for an upgrade of a part. This can be particularly useful when replacing or removing components from a Windows service that is MEF based and has an administration component to interact with the service.

The question came up last week about swapping out an MEF enabled DLL on the fly. Because .NET locks the assembly even in an MEF enabled application, you can't replace the DLL when you release the MEF parts in your code. The only way to replace a DLL without a little elbow grease, is to shut down the application, swap out the DLL, and then restart the application. So, after researching and finding a couple of decent examples and after applying the elbow grease, I came up with this solution. It's not pretty and it doesn't do much except prove how to do this. This is a copy of my Code Project article of the same title.

This example will only work with .NET 4.5 and above and assumes you already have an understanding of how MEF works and can get around with it without going into a tutorial on that.

We will be using the AppDomain class to create an application domain for the MEF components to run in. This will allow us to access the components at run time. I'll explain more of what's going on as we progress.

First, create a console application project called AppDomainTest. And in your Program class. I have a couple of paths set up here that point to where the MEF DLLs are found and where the AppDomainSetup will cache the DLLs while running. I'll explain more of that later.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.IO; namespace AppDomainTest { internal class Program {
private static AppDomain domain; [STAThread]
private static void Main() {
var cachePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "ShadowCopyCache");
var pluginPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins");
if (!Directory.Exists(cachePath)) {
Directory.CreateDirectory(cachePath);
} if (!Directory.Exists(pluginPath)) {
Directory.CreateDirectory(pluginPath);
} // This creates a ShadowCopy of the MEF DLL's (and any other DLL's in the ShadowCopyDirectories)
var setup = new AppDomainSetup {
CachePath = cachePath,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath
};
}
}
}

Now, create a class library project called AppDomainTestInterfaces. This library will contain the contract interface for the MEF libraries and the main application. Add a reference to this library in the main application. Delete the class file in there and create an interface called IExport.

1
2
3
4
5
6
7
namespace AppDomainTestInterfaces {

 public interface IExport {

  void InHere();
}
}

Next, create a couple of MEF class library projects. Add references to AppDomainTestInterfaces and System.ComponentModel.Composition in each library.

You'll want to set the build output to the bin\debug folder for the main application as shown. I put the compiled DLLs into a folder called Plugins under the main application bin\Debug folder so that they were easy to find and I could set up my code to be simple for this example. Use your own folder as needed.

Finally, create a class library project called AppDomainTestRunner and set a reference to it in the main application. Add references to System.ComponentModel.Composition,  System.ComponentModel.Composition.Registration, and System.Reflection.Context to add access to the necessary MEF components used in the rest of the example. And lastly, add a reference to AppDomainTestInterfaces.

Now we can get to the meat of this project.

In the AppDomainTestRunnerlibrary project, delete the Class1file and add a Runnerclass. This is the class that will deal with the MEF imports and exports, and we will see that the entire class runs in a separate AppDomain.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.IO;
using System.Linq;
using AppDomainTestInterfaces; namespace AppDomainTestRunner { public class Runner : MarshalByRefObject {
private CompositionContainer container;
private DirectoryCatalog directoryCatalog;
private IEnumerable<IExport> exports;
private static readonly string pluginPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"); public void DoWorkInShadowCopiedDomain() {
// Use RegistrationBuilder to set up our MEF parts.
var regBuilder = new RegistrationBuilder();
regBuilder.ForTypesDerivedFrom<IExport>().Export<IExport>(); var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Runner).Assembly, regBuilder));
directoryCatalog = new DirectoryCatalog(pluginPath, regBuilder);
catalog.Catalogs.Add(directoryCatalog); container = new CompositionContainer(catalog);
container.ComposeExportedValue(container); // Get our exports available to the rest of Program.
exports = container.GetExportedValues<IExport>();
Console.WriteLine("{0} exports in AppDomain {1}", exports.Count(), AppDomain.CurrentDomain.FriendlyName);
} public void Recompose() {
// Gimme 3 steps...
directoryCatalog.Refresh();
container.ComposeParts(directoryCatalog.Parts);
exports = container.GetExportedValues<IExport>();
} public void DoSomething() {
// Tell our MEF parts to do something.
exports.ToList().ForEach(e => e.InHere(););
}
}
}

Next, set up the MEF library code as shown below. This just shows that we actually are running in the DLLs. I created two of the exact same libraries, just naming the second AppDomainTestLib2.

 1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using AppDomainTestInterfaces; namespace AppDomainTestLib { public class Import : MarshalByRefObject, IExport { public void InHere() {
Console.WriteLine("In MEF library2: AppDomain: {0}",
AppDomain.CurrentDomain.FriendlyName);
}
}
}

Note the use of MarshalByRefObject, this will, in essence, mark this class as Serializable and enables access to objects across AppDomain boundaries, thereby gaining necessary access to methods and properties in the class residing in the hosted AppDomain.

Finally, set up the Main() method as follows. What we see here is the use of an AppDomainSetup object to define our AppDomain configuration. This establishes the shadow copying of the DLLs and where to shadow copy to. The CachePath parameter is optional, and only shown here as proof of what is happening. The parameter ShadowCopyFiles is a string parameter and accepts "true" or "false". The ShadowCopyDirectories parameter establishes which directory to shadow copy from.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 
using System;
using System.IO;
using AppDomainTestRunner; namespace AppDomainTest { internal class Program {
private static AppDomain domain; [STAThread]
private static void Main() {
var cachePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "ShadowCopyCache");
var pluginPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins");
if (!Directory.Exists(cachePath)) {
Directory.CreateDirectory(cachePath);
} if (!Directory.Exists(pluginPath)) {
Directory.CreateDirectory(pluginPath);
} // This creates a ShadowCopy of the MEF DLL's (and any other DLL's in the ShadowCopyDirectories)
var setup = new AppDomainSetup {
CachePath = cachePath,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath
}; // Create a new AppDomain then create an new instance of this application in the new AppDomain.
// This bypasses the Main method as it's not executing it.
domain = AppDomain.CreateDomain("Host_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
var runner = (Runner)domain.CreateInstanceAndUnwrap(typeof(Runner).Assembly.FullName, typeof(Runner).FullName); Console.WriteLine("The main AppDomain is: {0}", AppDomain.CurrentDomain.FriendlyName); // We now have access to all the methods and properties of Program.
runner.DoWorkInShadowCopiedDomain();
runner.DoSomething(); Console.WriteLine("\nHere you can remove a DLL from the Plugins folder.");
Console.WriteLine("Press any key when ready...");
Console.ReadKey(); // After removing a DLL, we can now recompose the MEF parts and see that the removed DLL is no longer accessed.
runner.Recompose();
runner.DoSomething();
Console.WriteLine("Press any key when ready...");
Console.ReadKey(); // Clean up.
AppDomain.Unload(domain);
}
}
}

About shadow copying: ShadowCopyFiles will take a copy of the DLLs that are actually used in the AppDomain and put them in a special folder then reference them from there. This allows the DLL in the Plugins (or any other configured folder) to be deleted or replaced during runtime. The original DLL will remain in the folder until either the next startup of the application or, in the example we will see, the DirectoryCatalog is refreshed and the CompositionContainer is recomposed and re-exported.

Now, when you run the application, you see the MEF DLLs run and within "Host_AppDomain".

At this point, you can go into the Plugins folder and delete a DLL then press any key in the console window to see what happens when runner.Recompose() is called. We then get proof that the recompose released our DLL, but only because of the ShadowCopyFiles parameter.

 

Now, open another instance of Visual Studio and create a class library called AppDomainTestLib3. Add the same references as before and don't set the output directory, we'll want to copy that in by hand. Set up its Import class code just the same as the previous AppDomainTestLib classes. Go ahead and compile it.

Next, run the application in the previous Visual Studio instance and stop at the first Console.ReadKey(). Delete a DLL from the Plugins folder and copy the new one in place. Press any key to continue...

Pretty cool, eh?

Finally, to actually replace a DLL, you must delete the previous DLL prior to implementing the new one. We can demonstrate the manual way of doing this by inserting the following code into Program.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// After removing a DLL, we can now recompose the MEF parts and see that the removed DLL is no longer accessed.
runner.Recompose();
runner.DoSomething(); Console.WriteLine("\nHere we will begin to replace Lib3 with an updated version. \nDelete the old one first DLL from the Plugins folder.");
Console.WriteLine("Press any key when ready...");
Console.ReadKey(); Console.WriteLine("Now showing that Lib3 is deleted.");
runner.Recompose();
runner.DoSomething(); Console.WriteLine("\nNext drop the new Lib3 in the Plugins folder.");
Console.WriteLine("Press any key when ready...");
Console.ReadKey(); runner.Recompose();
runner.DoSomething(); Console.WriteLine("Press any key when ready...");
Console.ReadKey(); // Clean up.
AppDomain.Unload(domain);

Leave the current AppDomainTestLib3 in the Plugins folder and run the application. Now, follow the prompts and when you get to "Here we will begin to replace Lib3 with an updated version.", make an observable change to the AppDomainTestLib3 and compile it. After completing the deletion of the old DLL, press any key to recompose the DLLs. Now, when you get the next prompt, drop the new DLL into the Plugins folder. Hit any key as usual. You should now see the response from your changed DLL.

The reason for the double runner.Recompose()calls is that the Exports signature for the DLL matches the previous version and MEF doesn't see a change since it doesn't look at FileInfo for differences. This then tells the AppDomain that the DLL hasn't changed either and the ShadowCopyFiles doesn't kick in to make that change. The simple work around is to delete the original, recompose, put the new one in place, and recompose one more time. The only disadvantage I can see in this is the performance of the application will drop momentarily during the recompose.

I've added a Github repository with the source. I also included in that source the ability to pass data between AppDomains. The source can be downloaded from http://github.com/johnmbaughman/MEFInAnAppDomain.

MEF and AppDomain z的更多相关文章

  1. MEF学习小结 z

    1.什么是MEF. MEF,全称是Managed Extensibility Framework.它是.NET Framework4.0的一个类库,其主要目的是为了创建可扩展的应用程序.按照官方说法就 ...

  2. AppDomain卸载与代理

    AppDomain卸载与代理 涉及内容: 反射与MEF解决方案 AppDomain卸载与代理 WinForm.WcfRestService示 插件系统的基本目的是实现宿主与组件的隔离,核心是作为接驳约 ...

  3. 动态加载与插件系统的初步实现(一):反射与MEF解决方案

    涉及内容: 反射与MEF解决方案 AppDomain卸载与代理 WinForm.WcfRestService示 PRRT1: 反射实现 插件系统的基本目的是实现宿主与组件的隔离,核心是作为接驳约定的接 ...

  4. 【Python】使用torrentParser1.03对多文件torrent的分析结果

    Your environment has been set up for using Node.js 8.5.0 (x64) and npm. C:\Users\horn1>cd C:\User ...

  5. CLR via C# 读书笔记 6-2 不同AppDomain之间的通信 z

    跨AppDomain通信有两种方式 1.Marshal By reference : 传递引用 2.Marshal By Value : 把需要传递的对象 通过序列化反序列化的方式传递过去(值拷贝) ...

  6. C#进阶系列——MEF实现设计上的“松耦合”(终结篇:面向接口编程)

    序:忙碌多事的八月带着些许的倦意早已步入尾声,金秋九月承载着抗战胜利70周年的喜庆扑面而来.没来得及任何准备,似乎也不需要任何准备,因为生活不需要太多将来时.每天忙着上班.加班.白加班,忘了去愤,忘了 ...

  7. MEF实现设计上的“松耦合”(三)

    1.面向接口编程:有一定编程经验的博友应该都熟悉或者了解这种编程思想,层和层之间通过接口依赖,下层不是直接给上层提供服务,而是定义一组接口供上层调用.至于具体的业务实现,那是开发中需要做的事情,在项目 ...

  8. [MEF插件式开发] 一个简单的例子

    偶然在博客园中了解到这种技术,顺便学习了几天. 以下是搜索到一些比较好的博文供参考: MEF核心笔记 <MEF程序设计指南>博文汇总 先上效果图 一.新建解决方案 开始新建一个解决方案Me ...

  9. 在.NET Core中使用MEF

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:微软的可托管扩展框架也移植到.NET Core上了. 可托管扩展框架(Managed ...

随机推荐

  1. 2018-2019-2 网络对抗技术 20165301 Exp2 后门原理与实践

    2018-2019-2 网络对抗技术 20165301 Exp2 后门原理与实践 实验内容 (1)使用netcat获取主机操作Shell,cron启动 (2)使用socat获取主机操作Shell, 任 ...

  2. IdentityServer4结合AspNetCore.Identity实现登录认证踩坑填坑记录

    也可以自定义实现,不使用IdentityServer4.AspNetIdentity这个包,当然还要实现其他接口IResourceOwnerPasswordValidator. IProfileSer ...

  3. MVC4是不是类似于html页+ashx页之间用JSON通过AJAX交换数据这种方式、?

    不是,可以讲mvc模式是借鉴于java下面的mvc开发模式,为开发者公开了更多的内容和控制,更易于分工合作,与单元测试,借用官方的说法:MVC (Model.View.Controller)将一个We ...

  4. React 与 Redux 在生产环境中的实践总结

    React 与 Redux 在生产环境中的实践总结 前段时间使用 React 与 Redux 重构了我们360netlab 的 开放数据平台.现将其中一些技术实践经验总结如下: Universal 渲 ...

  5. bzoj 1185

    题目大意: 给你n个点求最小矩形覆盖. 思路:枚举凸包上的边然后,旋转卡壳找三个相应的为止把矩形的四个点求出来. #include<bits/stdc++.h> #define LL lo ...

  6. WinSCP命令行操作

    WinSCP命令行操作     WinSCP是一个Windows环境下使用SSH的开源图形化SFTP客户端.同时支持SCP协议.它的主要功能就是在本地与远程计算机间安全的复制文件. 直接在cmd下输入 ...

  7. catalan数的新理解

    catalan数的新理解h[5]==h[4][0]+h[3][1]+h[2][2]+h[1][3]+h[0][4];对于这种递推式就是catalan数

  8. 004.FTP匿名用户访问

    一 匿名用户配置项 [root@imxhy~]# vi /etc/vsftpd/vsftpd.conf anonymous_enable #允许匿名用户访问 anon_upload_enable #允 ...

  9. Vue之双向绑定原理动手记

    Vue.js的核心功能有两个:一是响应式的数据绑定系统,二是组件系统.本文是通过学习他人的文章,从而理解了双向绑定原理,从而在自己理解的基础上,自己动手实现数据的双向绑定. 目前几种主流的mvc(vm ...

  10. 手动搭建ABP2.1.3——基础框架

    一.基础层搭建 1,创建一个空解决方案 2,层结构 Demo.Core[v:4.6.1]:类库 Demo.EntityFramework[v:4.6.1]:类库(引用Demo.Core) Demo.A ...