如何在应用程序域中监视影子复制程序集的原始版本何时更改

时间:2014-09-24 19:35:10

标签: c# .net appdomain

我正在创建一个服务,在不同的应用程序域中托管和运行其他较小的服务(类似于迷你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);

这基本上会设置一个应用程序域,启用卷影副本,并在新的应用程序域中调用RegisterServiceRegisterService方法将创建一个对象实例,它将阴影复制的程序集加载到内存中。

例如,服务可能在程序集ServiceFoo中包含类型Foo.dllFoo.dll位于service.FullPath,因此在加载应用域时会复制阴影。从现在开始,我可以删除或修改 Foo.dll目录中的原始service.FullPath,这很棒。

然而,当某人修改原始Foo.dll时(例如,他们通过网络复制新版本),我希望收到通知,以便我可以卸载旧的应用域名并使用新版本的程序集再次创建它。

基本上,我在这里要做的是为管理员提供部署新版本服务的能力,而不会中断在同一进程中运行的其他服务。

我的问题:如果Foo.dll被修改,或者ShadowCopyDirectories目录中的任何文件发生变化,我该如何收到通知?我确信我可以每隔几秒检查这些目录中的时间戳,但似乎应该有更好的方法。这种情况的最佳方法是什么?

更新

我目前的想法全都解决FileWatcher。但是,找出要观看的文件证明了我的难度。

创意1:监控service.FullPath,这是包含可能更改的文件的目录。但是,多个服务可能存在于此目录中。如果我监视目录,对一个文件的更改可能会导致在实际不使用该文件的服务上重新启动错误。

创意2:解析service.TypeName,这是一个包含完全限定类型名称的字符串。 .NET提供了解析这些字符串的方法,例如Type.GetType(string),这将允许我访问代码库。然而,第二个我将程序集加载到父app域,它变得锁定,所以我不能再改变它。我可以尝试手动解析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上调用。

1 个答案:

答案 0 :(得分:0)

所以这就是我所做的。首先,我创建了一个名为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原始实例,然后可以设置文件系统监视器并提醒控制器需要重新加载服务。

希望这有助于某人!