如何监视卷影复制程序集的原始版本何时在应用程序域中更改
本文关键字:何时 版本 原始 应用程序域 程序集 何监视 监视 复制 | 更新日期: 2023-09-27 18:24:12
我正在创建一个服务,该服务在单独的应用程序域中托管和运行其他较小的服务(有点像迷你IIS)。当每个服务在启动时注册时,我运行以下代码:
AppDomainSetup setup = new AppDomainSetup
{
LoaderOptimization = LoaderOptimization.MultiDomain,
ShadowCopyDirectories = service.FullPath, // Directory service binary lives in
ShadowCopyFiles = Convert.ToString(true)
};
AppDomain domain = AppDomain.CreateDomain(service.Name, null, setup);
ServiceDomain s = domain.CreateInstanceAndUnwrap<ServiceDomain>();
s.RegisterService(service, DefaultPort);
这基本上设置了一个应用程序域,启用卷影复制,并在新的应用程序域中调用RegisterService
。RegisterService
方法将创建一个对象的实例,该实例将把影子复制的程序集加载到内存中。
例如,服务可能在程序集Foo.dll
中包含类型ServiceFoo
。Foo.dll
位于service.FullPath
中,因此在加载应用程序域时会进行卷影复制。从这一点开始,我可以删除或修改service.FullPath
目录中的原始Foo.dll
,这很好。
但是,当有人修改原始Foo.dll
(例如,他们通过网络复制到新版本中)时,我希望得到通知,这样我就可以卸载旧的应用程序域,并使用程序集的新版本重新创建它。
基本上,我在这里尝试做的是为管理员提供部署新版本服务的能力,而不会中断在同一进程中运行的其他服务。
我的问题:当Foo.dll
被修改,或者ShadowCopyDirectories
目录中的任何文件发生更改时,如何通知我?我确信我可以每隔几秒钟检查一次这些目录中的时间戳,但似乎应该有更好的方法。对于这种情况,最好的方法是什么?
更新:
我目前的想法都是围绕FileWatcher
来解决的。然而,我很难弄清楚要看哪个文件。
理念1:监控service.FullPath
,它是包含可能更改的文件的目录。但是,此目录中可能存在多个服务。如果我监视目录,对一个文件的更改可能会导致实际不使用该文件的服务错误地重新启动。
想法2:解析service.TypeName
,这是一个包含完全限定类型名称的字符串。NET提供了解析这些字符串的方法,例如Type.GetType(string)
,这样我就可以访问代码库。然而,当我将程序集加载到父应用程序域时,它就被锁定了,所以我不能再更改它了。我可以尝试手动解析TypeName
,但这种语法相当复杂。有很多库试图解析这种语法。
理念3:监控AssemblyLoad
事件:
domain.AssemblyLoad += (sender, args) =>
{
string test = AppDomain.CurrentDomain.FriendlyName;
Uri fileUri = new Uri(args.LoadedAssembly.CodeBase);
FileInfo fileInfo = new FileInfo(fileUri.LocalPath);
};
当域加载程序集时,会触发此操作。我可以检测fileInfo.Directory
是否与service.FullPath
相同,如果是,我可以对该文件进行监视。一个问题。此委托将在domain
的上下文中运行。我无法访问ServiceResolver
或根域中的任何内容。
想法4:在我呼叫s.RegisterService
后,检查domain.GetAssemblies()
并尝试查找来自service.FullPath
的。然而,当我运行时:
var assemblies = domain.GetAssemblies();
它立即抛出异常:
在程序集中键入"System.Reflection.Emit.InternalAssemblyBuilder"'mscorlib,版本=4.0.0.0,区域性=中性,PublicKeyToken=b77a5c561934e089'未标记为可序列化。
我不太清楚为什么会这样。我猜GetAssemblies()
只应该在当前的AppDomain上调用。
下面是我所做的。首先,我创建了一个名为ServiceDomainProxy
的新类,它看起来像这样:
internal class ServiceDomainProxy : MarshalByRefObject
{
private ServiceController controller;
private List<FileSystemWatcher> watchers;
public ServiceDomainProxy(ServiceController controller)
{
this.controller = controller;
}
public void CreateMonitor(Uri fileUri)
{
if(watchers == null)
watchers = new List<FileSystemWatcher>();
// Setup a FileWatcher on type, notify controller whenever it reloads
FileInfo file = new FileInfo(fileUri.LocalPath);
FileSystemWatcher watcher = new FileSystemWatcher
{
Path = file.DirectoryName,
Filter = file.Name
};
watcher.Changed += fileChanged;
watcher.EnableRaisingEvents = true;
watchers.Add(watcher);
}
void fileChanged(object sender, FileSystemEventArgs e)
{
Controller.Log.Info("Reloading Service Assembly: {0}", e.Name);
// TODO: Call the Reload method on controller
}
}
这个类基本上充当域之间的代理。接下来,我修改了ServiceDomain
类,使其具有一个接受ServiceDomainProxy
实例的构造函数:
public ServiceDomain(ServiceDomainProxy proxy)
{
this.proxy = proxy;
}
我必须更新我的CreateInstanceAndUnwrap
调用,以便也传入ServiceDomainProxy
的实例:
Type t = typeof(ServiceDomain);
ServiceDomain s = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false, BindingFlags.Default, null, new object[] { proxy }, null, null);
然后,在ServiceDomain.RegisterService
中,我可以用ServiceDomainProxy
:注册已解析的类型
proxy.CreateMonitor(new Uri(service.ResolveType().Assembly.CodeBase));
这会跨应用程序域边界调用ServiceDomainProxy
的原始实例,然后可以设置文件系统监视器并提醒控制器需要重新加载服务。
希望这能帮助到别人!