强大的WCF宿主

同步自我的个人博客

需求重温

接着上一篇来解决第二个问题。首先让我们重温一下问题出现的场景:

描述这样一种需求:有一个不能随便重启的程序使用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键推出本程序。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值