同步自我的个人博客
需求重温
接着上一篇来解决第二个问题。首先让我们重温一下问题出现的场景:
描述这样一种需求:有一个不能随便重启的程序使用MEF完成了插件机制,插件以dll的形式放在Add-Ins目录下,启动后从Add-Ins目录加载插件,然后在程序运行过程中,插件需要增加、更新、删除等操作,希望不重启程序但是又能用新的插件。
这里不能随便重启的程序就是一个WCF服务的宿主(Host),因为这个Host里面放了很多的服务,其中一些正在运行,此时如果更新了一个服务的dll,这个宿主如果重启,会引起其他服务中断,导致无法预测的问题。
所以我们需要做出这个宿主程序,满足以上的要求。
宿主程序
首先让宿主程序监视存放服务dll的目录。
static Dictionary<string, AppDomain> appDomains = new Dictionary<string, AppDomain>(); static void Main(string[] args) { // can't find settings at *.exe.config file? look at current directory const string pattern = "*.dll"; string dropPath = Path.GetFullPath( args.Length == 0 ? ConfigurationManager.AppSettings["DropPath"] ?? Environment.CurrentDirectory : args[0]); Console.Title = dropPath + Path.DirectorySeparatorChar + pattern; if (!Directory.Exists(dropPath)) throw new DirectoryNotFoundException(dropPath); // sets up file system monitoring DelayedFileSystemWatcher dfsw = new DelayedFileSystemWatcher(dropPath, pattern); dfsw.Created += new FileSystemEventHandler(dfsw_CreatedOrChanged); dfsw.Changed += new FileSystemEventHandler(dfsw_CreatedOrChanged); dfsw.Deleted += new FileSystemEventHandler(dfsw_Deleted); // before start monitoring disk, load already existing assemblies foreach(string assemblyFile in Directory.GetFiles(dropPath, pattern)) { Create(Path.GetFullPath(assemblyFile)); } dfsw.EnableRaisingEvents = true; Console.ReadLine(); // stay away from ENTER key Console.ResetColor(); }
文件系统监视器是使用DelayedFileSystemWatcher
,这个类增加了时间阈值,如果多个时间在时间阈值内发生,则将事件排队,并剔除重复事件。通过这些事件来维护一张AppDomain的列表,如果发生删除事件则卸载相应的Appdomain,如果是Created或者Changed事件,则卸载相应的AppDomain,然后重建AppDomain放入列表中。使用Dictionary<string, AppDomain>存储,key是服务程序集的全路径,AppDomain是服务建立所在的域。
建立应用程序域
在这一步,我将在外部域里新建一个继承自
MarshalByRefObject类的子类的实例,并用它来,有几点需要注意:
每个服务都有相应的配置文件,在本例中没有配置文件,或者取消配置文件,或者读取统一的配置文件。
需要开启影像复制(ShadowCopyFiles属性),否则dll将会被锁定,无法替换。
使用CreateInstanceAndUnwrap方法实例化类的过程是发生在新建的域里的。
代码面前一切真相大白。
class RemoteProxy : MarshalByRefObject { static public AppDomain Start(string assemblyFile) { AppDomain domain = null; try { AppDomainSetup info = new AppDomainSetup(); info.ShadowCopyFiles = "true"; info.ConfigurationFile = assemblyFile + ".config"; AppDomain appDomain = AppDomain.CreateDomain( assemblyFile, null, info); RemoteProxy proxy = (RemoteProxy)appDomain .CreateInstanceAndUnwrap( Assembly.GetExecutingAssembly().FullName, typeof(RemoteProxy).FullName); if (!proxy.LoadServices(assemblyFile)) AppDomain.Unload(appDomain); else domain = appDomain; } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); } return domain; } // ... }
承载WCF服务
到上面还是没能启动服务,只是新建了一个新的应用程序域。现在该加载程序集,并启动服务。本例中所有服务的类都具有[ServiceContract]特性(attribute)。
bool hasServices = false; public bool LoadServices(string assemblyFile) { try { AssemblyName assemblyRef = new AssemblyName(); assemblyRef.CodeBase = assemblyFile; Assembly assembly = Assembly.Load(assemblyRef); Type[] serviceTypes = LocateServices(assembly.GetTypes()); foreach (Type serviceType in serviceTypes) { try { Create(serviceType); } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); } } } catch(Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); } return hasServices; }
所以说到底服务是什么?服务只是一个一个类,具有特殊接口的类,只有放在宿主中,宿主运行起来了,并监听某个端口,才可以说是服务运行起来了。
private void Create(Type serviceType) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Starting {0}", serviceType.FullName); try { ServiceHost host = new ServiceHost(serviceType, new Uri[0]); foreach (ServiceEndpoint endpoint in host.Description.Endpoints) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(" {0}", endpoint.Address); endpoint.Behaviors.Add(new MonitorBehavior()); } host.Open(); hasServices = true; } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); } }
把那些隐藏的信息输出到屏幕
本来服务是监听端口,给远程调用者调用的。本例为了便于测试,服务开启后,监听信息输出到控制台,是哟on个MonitorBehavior类实现。
class MonitorBehavior : IEndpointBehavior { // ... empty methods removed ... \\ public void ApplyDispatchBehavior( ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.MessageInspectors .Add(new MonitorDispatcher()); } class MonitorDispatcher : IDispatchMessageInspector { public object AfterReceiveRequest( ref Message request, IClientChannel channel, InstanceContext instanceContext) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine( "{0:HH:mm:ss.ffff}\t{1}\n\t\t{2} ({3} bytes)\n\t\t{4}", DateTime.Now, request.Headers.MessageId, request.Headers.Action, request.ToString().Length, request.Headers.To); return null; } public void BeforeSendReply( ref Message reply, object correlationState) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine( "{0:HH:mm:ss.ffff}\t{1}\n\t\t{2} ({3} bytes)", DateTime.Now, reply.Headers.RelatesTo, reply.Headers.Action, reply.ToString().Length); } } }
备注
本文附件中有两个工程,一个是服务的dll,一个是我们文中实现的强大的宿主程序。
- ConsoleHoster : 宿主程序。首先运行它,然后改变app.config中配置的监视的文件目录,回车即可推出本程序。
ServiceImplementation : 服务类的实现。其中包括服务端和客户端,服务端在宿主中起来后,运行客户端,按任何键都会引发服务调用,按ESC键推出本程序。