ESFramework介绍之(8)-- 客户端插件IPassiveAddin

    前文已经提到了,在IServerAgent的基础上,客户端也可以采用插件的结构形式,客户端插件需要实现IPassiveAddin接口。
    我的想法是,当客户端主程序加载一个新的PassiveAddin时,可以在某个菜单的子Items上添加一项,当双击这个子菜单项时,则弹出该客户端插件提供的“业务操作窗体”。这只是使用客户端插件的可行方式之一,你完全可以根据你的应用来决定使用形式。IPassiveAddin接口定义如下:

 1     /// <summary>
 2     /// IPassiveAddin 用于客户端的插件。通常一个PassiveAddin对应着一个服务端的功能插件FunAddin
 3     /// zhuweisky 2006.03.13
 4     /// </summary>
 5     public interface IPassiveAddin : IAddin
 6     {
 7         Type AddinFormType{get ;} //AddinFormType必须实现IAddinForm接口
 8     }
 9 
10     public interface IPassiveAddinForm
11     {
12         //PassiveAddin通过IServerAgent发送请求并获取结果
13         void Initialize(IServerAgent serverAgent ,string userID) ;
14     }

    IPassiveAddin直接从IAddin继承,仅仅增加了一个属性AddinFormType,这个属性就是前面说的客户端插件提供的“业务操作窗体”。“业务操作窗体”必须从IPassiveAddinForm接口继承。
    “业务操作窗体”只有通过暴露的Initialize方法获取IServerAgent引用后,才能发送请求获取结果。Initialize方法的第二个参数说明当前时哪个用户在操作,这样客户端插件在构建请求消息时,需要将userID填充到请求消息的消息头中去,这样服务器才会知道这个消息的来源。

    下面的代码说明了客户端主程序是如何加载IPassiveAddin的:

 1          private void LoadPassiveAddins()
 2         {
 3             this.lIToolStripMenuItem_addin.DropDownItems.Clear();
 4 
 5             string directory = System.IO.Directory.GetParent(System.Windows.Forms.Application.ExecutablePath).FullName;
 6             this.addinManagement.LoadAllAddins(directory, true);
 7 
 8             foreach (IAddin addin in this.addinManagement.AddinList)
 9             {
10                 IPassiveAddin passiveAddin = addin as IPassiveAddin;
11                 if (passiveAddin != null)
12                 {
13                     ToolStripItem item = new ToolStripMenuItem(passiveAddin.ServiceName, null, new EventHandler(this.OnAddinMenuClicked));
14                     item.Tag = passiveAddin;
15                     this.lIToolStripMenuItem_addin.DropDownItems.Add(item);
16                 }
17             }
18         }
19 
20         private void OnAddinMenuClicked(object sender, EventArgs e)
21         {
22             try
23             {
24                 ToolStripItem item = (ToolStripItem)sender;
25                 IPassiveAddin passiveAddin = (IPassiveAddin)item.Tag;
26                 Form addinForm = (Form)Activator.CreateInstance(passiveAddin.AddinFormType);
27                 ((IPassiveAddinForm)addinForm).Initialize(this.tcpServerAgent ,this.currentUserID);
28                 addinForm.Show();
29             }
30             catch (Exception ee)
31             {
32                 ee = ee;
33             }
34         }

    上述的介绍没有什么难点,仔细体会一下都能明白,就不多说了。这里我给出一个测试用的功能插件和对应的客户端插件示例。示例的功能插件用于从http://www.webservicex.net/globalweather.asmx通过WebService获取城市的天气信息,而客户端插件则用于为用户提供这项服务。

先看服务端功能插件实现:

WeatherPreAddin
  1 public class WeatherPreAddin :IFunAddin
  2     {
  3         private IContractHelper contractHelper = new ContractHelper() ;
  4 
  5         public WeatherPreAddin()
  6         {
  7             
  8         }
  9 
 10         #region IAddin 成员
 11 
 12         public string ServiceName
 13         {
 14             get
 15             {
 16                 // TODO:  添加 WeatherPreAddin.ServiceName getter 实现
 17                 return "天气预测服务";
 18             }
 19         }
 20 
 21         public void OnLoading()
 22         {
 23             // TODO:  添加 WeatherPreAddin.OnLoading 实现
 24         }
 25 
 26         public string CatalogName
 27         {
 28             get
 29             {
 30                 // TODO:  添加 WeatherPreAddin.CatalogName getter 实现
 31                 return "";
 32             }
 33         }
 34 
 35         public int ServiceKey
 36         {
 37             get
 38             {
 39                 // TODO:  添加 WeatherPreAddin.ServiceKey getter 实现
 40                 return 987 ;
 41             }
 42         }
 43 
 44         public string Description
 45         {
 46             get
 47             {
 48                 // TODO:  添加 WeatherPreAddin.Description getter 实现
 49                 return "";
 50             }
 51         }
 52 
 53         public void BeforeTerminating()
 54         {
 55             // TODO:  添加 WeatherPreAddin.BeforeTerminating 实现
 56         }
 57 
 58         public bool Enabled
 59         {
 60             get
 61             {
 62                 // TODO:  添加 WeatherPreAddin.Enabled getter 实现
 63                 return true;
 64             }
 65             set
 66             {
 67                 // TODO:  添加 WeatherPreAddin.Enabled setter 实现
 68             }
 69         }
 70 
 71         public float Version
 72         {
 73             get
 74             {
 75                 // TODO:  添加 WeatherPreAddin.Version getter 实现
 76                 return 1;
 77             }
 78         }
 79 
 80         public string AddinType
 81         {
 82             get
 83             {
 84                 // TODO:  添加 WeatherPreAddin.AddinType getter 实现
 85                 return null;
 86             }
 87         }
 88 
 89         public AddinAppendixInfo AddinAppendixInfo
 90         {
 91             get
 92             {
 93                 // TODO:  添加 WeatherPreAddin.AddinAppendixInfo getter 实现
 94                 return null;
 95             }
 96         }
 97 
 98         #endregion
 99 
100         #region IDataDealer 成员
101 
102         public ESFramework.Network.NetMessage DealRequestMessage(ESFramework.Network.NetMessage reqMsg)
103         {    
104             
105             try
106             {
107                 string url = "http://www.webservicex.net/globalweather.asmx" ;
108                 string[] args = new string[2] ;
109                 args[0] = this.contractHelper.GetStrFromStream(reqMsg.Body ,reqMsg.BodyOffset ,reqMsg.Header.MessageBodyLength) ;
110                 args[1] = "China" ;
111                 object result = WebServiceHelper.InvokeWebService(url ,"GetWeather" ,args) ;
112             
113                 XmlParser parser = new XmlParser(result.ToString() ,null) ;
114                 WeatherPredictionContract body = new WeatherPredictionContract(this.contractHelper) ;
115                 body.Pressure = parser.GetSingleLayerConfigValue("Pressure") ;
116                 body.PressureLen = this.contractHelper.GetBytesFromStr(body.Pressure).Length ;
117 
118                 body.PreTime  = parser.GetSingleLayerConfigValue("Time") ;
119                 body.PreTimeLen = this.contractHelper.GetBytesFromStr(body.PreTime).Length ;
120 
121                 body.Temprature = parser.GetSingleLayerConfigValue("Temperature") ;
122                 body.TempratureLen = this.contractHelper.GetBytesFromStr(body.Temprature).Length ;
123 
124                 body.Visbility  = parser.GetSingleLayerConfigValue("Visibility") ;
125                 body.VisbilityLen = this.contractHelper.GetBytesFromStr(body.Visbility).Length ;
126 
127                 body.Wind  = parser.GetSingleLayerConfigValue("Wind") ;
128                 body.WindLen = this.contractHelper.GetBytesFromStr(body.Wind).Length ;
129 
130                 reqMsg.Header.MessageBodyLength = body.GetStreamLength() ;
131 
132                 return new NetMessage(reqMsg.Header ,body.ToStream() ,0) ;
133 
134             }
135             catch(Exception ee)
136             {
137                 ee = ee ;
138                 reqMsg.Header.MessageBodyLength = 0 ;
139                 reqMsg.Header.Result = ServiceResultType.FailureByOtherCause ;
140                 return new NetMessage(reqMsg.Header ,null);
141             }
142         }
143 
144         #endregion
145     }

主要是DealRequestMessage方法的实现,代码非常简单,通过WebService获取指定城市的天气情况,将返回的XML解析封装成IContract,然后返回给客户端。

接下来看客户端插件的实现,分为两步:首先是“业务操作窗体”界面设计。

 该窗体要从IPassiveAddinForm接口继承。当点击按钮时,处理代码为:

button1_Click
        private void button1_Click(object sender, System.EventArgs e)
        {
            string cityName = ESFramework.Common.ChsToSpellConverter.Convert(this.comboBox1.SelectedItem.ToString()) ;
            WeatherReqContract body = new WeatherReqContract(this.contractHelper) ;
            body.cityName = cityName ;
            MessageHeader header = new MessageHeader(this.contractHelper) ;
            header.TypeKey = int.Parse(this.textBox_asCityCode.Text.Trim()) ;
            header.ServiceKey = 987 ;
            header.UserID     = this.curUserID ;
            header.UserIDLen  = this.contractHelper.GetBytesFromStr(this.curUserID).Length ;
            header.MessageBodyLength = body.GetStreamLength() ;

            ESFramework.Network.Message msg = new ESFramework.Network.Message(header ,body) ;
            NetMessage resMsg = this.theAgent.CommitRequest(msg ,DataPriority.Common ,true) ;
            if(resMsg.Header.Result != ServiceResultType.ServiceSucceed)
            {
                MessageBox.Show("没有发现对应的服务~!") ;
                return ;
            }

            WeatherPredictionContract resContract = new WeatherPredictionContract(this.contractHelper) ;
            resContract.FillMyself(resMsg.Body ,resMsg.BodyOffset) ;

            this.groupBox1.Text = "服务结果--" + this.comboBox1.SelectedItem.ToString() ;

            this.label_pressure.Text = resContract.Pressure ;
            this.label_temp.Text     = resContract.Temprature ;
            this.label_vis.Text      = resContract.Visbility ;
            this.label_wind.Text     = resContract.Wind ;
            this.label_time.Text     = resContract.PreTime ;
        }

    注意,theAgent成员即是通过Initialize传入的IServerAgent引用!

接着是IPassiveAddin实现:

WeatherPassiveAddin
  1     public class WeatherPassiveAddin :IPassiveAddin
  2     {
  3         public WeatherPassiveAddin()
  4         {
  5         }
  6 
  7         #region IPassiveAddin 成员
  8 
  9         public Type AddinFormType
 10         {
 11             get
 12             {                
 13                 return typeof(AddinForm); //AddinForm即前面实现的业务窗体
 14             }
 15         }
 16 
 17         #endregion
 18 
 19         #region IAddin 成员
 20 
 21         public string ServiceName
 22         {
 23             get
 24             {
 25                 // TODO:  添加 WeatherPassiveAddin.ServiceName getter 实现
 26                 return "天气预测服务";
 27             }
 28         }
 29 
 30         public void OnLoading()
 31         {
 32             // TODO:  添加 WeatherPassiveAddin.OnLoading 实现
 33         }
 34 
 35         public string CatalogName
 36         {
 37             get
 38             {
 39                 // TODO:  添加 WeatherPassiveAddin.CatalogName getter 实现
 40                 return null;
 41             }
 42         }
 43 
 44         public int ServiceKey
 45         {
 46             get
 47             {
 48                 // TODO:  添加 WeatherPassiveAddin.ServiceKey getter 实现
 49                 return 987;
 50             }
 51         }
 52 
 53         public string Description
 54         {
 55             get
 56             {
 57                 // TODO:  添加 WeatherPassiveAddin.Description getter 实现
 58                 return null;
 59             }
 60         }
 61 
 62         public void BeforeTerminating()
 63         {
 64             // TODO:  添加 WeatherPassiveAddin.BeforeTerminating 实现
 65         }
 66 
 67         public bool Enabled
 68         {
 69             get
 70             {
 71                 // TODO:  添加 WeatherPassiveAddin.Enabled getter 实现
 72                 return true;
 73             }
 74             set
 75             {
 76                 // TODO:  添加 WeatherPassiveAddin.Enabled setter 实现
 77             }
 78         }
 79 
 80         public float Version
 81         {
 82             get
 83             {
 84                 // TODO:  添加 WeatherPassiveAddin.Version getter 实现
 85                 return 1;
 86             }
 87         }
 88 
 89         public string AddinType
 90         {
 91             get
 92             {
 93                 // TODO:  添加 WeatherPassiveAddin.AddinType getter 实现
 94                 return null;
 95             }
 96         }
 97 
 98         public AddinAppendixInfo AddinAppendixInfo
 99         {
100             get
101             {
102                 // TODO:  添加 WeatherPassiveAddin.AddinAppendixInfo getter 实现
103                 return null;
104             }
105         }
106 
107         #endregion
108     }

    需要格外注意要实现AddinFormType属性,就是前面实现的“业务窗体”类型。

 

下图是功能服务器加载功能插件的截图:

 

下图是客户端加载客户插件后的截图:

 

下图是客户端插件提供服务的截图:

 

     经过上述的介绍,读者应该对开发服务端的功能插件和客户端插件有些了解了。快结束的时候,再为下篇blog开个头。当我们开发了客户端插件和服务端插件后,做调试是一项非常麻烦的工作,因为不仅要启动应用服务器,还要启动客户端主程序、功能服务器才行。为了简化这个过程,我实现了一个Bridge应用程序,只需要加载一pair插件(服务端插件和对应的客户插件),即可进行两个插件的调试,而不用在启动客户端、AS、FS了。

    感谢关注!

上一篇:ESFramework介绍之(7)-- 服务器代理IServerAgent
转到  :ESFramework 可复用的通信框架(序) 

时间: 2024-07-30 01:54:35

ESFramework介绍之(8)-- 客户端插件IPassiveAddin的相关文章

ESFramework介绍之(9)-- 插件对(Addin Pair)调试“框架”

    使用ESFramework开发C/S(通常为4层.3层也没问题)应用,当需要增加一项新的业务时,我们需要做的仅仅是开发两个插件,一个是服务端的业务功能插件(FunAddin),一个是客户端插件(PassiveAddin),这两个插件合在一起称为Addin Pair.开发这两个插件,只需要关注于业务,而其它与业务无关的比如网络通信.加密.数据安全,都不用管.ESFramework很好的将这些关注点分离开来,使得写"业务"插件的程序员的工作变得非常单纯,在ESFramework介绍

ESFramework介绍之(32)―― Tcp客户端核心组件关系图

    如果你的客户端基于ESFramework构建,并使用Tcp与服务端通信.通过这篇文章你将知道如何迅速.高效地创建客户端应用.     ESFramework对基于Tcp客户端的支持主要在ESFramework.Network.Tcp.Passive命名空间,下图给出了该命名空间中的核心组件,以及这些组件之间的关系:    下面解释一下这些组件的作用.职责.我们从最底层的通信组件NetworkStream向上看:(1)NetworkStream 即是System.Net.Sockets.N

ESFramework介绍之(20)―― 插件自动升级

    当我们的服务平台搭建成功后,所需要做的主要事情就是开发服务端功能插件(IFunAddin)和客户端插件(IPassiveAddin),每个插件对(AddinPair)实现了一组相似或相近的需求/功能.     好了,我们已经开发了十多对插件对,然后分别XCopy到了各个服务器节点上,"整个系统"已经投入了运行.通过前面的介绍(回顾),相信大家对我们的"整个系统"有了个大致的映像.我们的IRAS服务器通常只存在于一个节点上,而我们的AS和对应的多个FS通常分布

ESFramework介绍之(29)―― 插件公共设施 AddinUtil

    (本文适用于 ESFramework V0.2+)    不知你是否还记得,前面我们讲过,ESFramework规定了插件有如下特点: (1)一个插件是一个独立的物理单元.它可以独立的提供一项完整的服务(功能),而不需要依赖于其它插件. (2)插件能自我描述 ―― 插件的所有对外的发布信息都由插件自己内部提供,而不依赖于外部文件或注册表. (3)插件能自我管理 ―― 插件如果需要配置信息,则插件自己能读取和修改配置信息,而不是框架来完成这些事情.(4)插件自我独立   ―― 一个插件不得

ESFramework介绍之(13)-- 功能插件处理器工厂

    上文讲述的是AS中的基于连接池的消息处理器,现在我们把焦点转移到功能服务器FS上来,看看FS上消息分派的过程.当FS接收到到一个请求后,会从已加载的功能插件列表中选择一个合适的插件来处理这个消息,而每一个功能插件就相当于一个消息处理器.FS和AS的结构一致:    要注意的是,功能服务器FS上收到的所有消息都应该交给功能插件来处理,不存在其它的处理方式.这是使得FS"纯粹"的必须要求.上图已经很清楚的表示了功能插件处理器工厂的位置和作用.它借助插件管理器实现"工厂&q

ESFramework介绍之(26)-- 支持复杂插件(InnerDealer 和 InnerDispatcher)

    (本文内容适合于 ESFramework V0.2+)    通常,最单纯的情况是一个插件对应某一特定类型的功能请求,但是,在有的应用中也会出现这样的情况,有多种类型的功能请求相互关联.并且可能交叉,如果是这样,对应每种类型的请求都开发一个插件可能会非常困难,因为这可能会牵涉到插件之间的相互引用/访问,这违背了插件的"自治"性.最好的办法还是将它们放在一个插件中,通过ServiceItemIndex(你一定还记得消息头定义中除了ServiceKey外还有个ServiceItem

ESFramework介绍之(22)―― 服务器系统自动升级

    (本文名字取为"服务器系统自动升级",实际上适用于所有应用程序自动升级的情况.)    前文介绍了在服务器或客户端应用程序运行的过程中,插件如何自动升级.更新.基于前文相同的理由,AS.FS.IRAS也需要有自动升级的功能.     与插件在运行时动态更新不同,服务器系统无法在运行时动态更新,只有在服务器系统重新启动的时候,才是自动升级的切入点.(1)对于功能服务器FS,可以采用持续/逐个更新的方式,即依次重启每个功能服务器.这样可以避免功能服务被中断的情况发生.需要注意的是,

ESFramework介绍之(7)-- 服务器代理IServerAgent

    (本文原作于2006.03.15,第一次修正于2006.06.06,修正后适用于ESFramework V0.3+)     (本文是ESFramework对客户端开发的支持特性之一 ,如果要按顺序阅读,请转到ESFramework介绍(序))         分布式系统的构建一般有两种模式,一是基于消息(如Tcp,http等),一是基于方法调用(如RPC.WebService.Remoting).深入想一想,它们其实是一回事.如果你了解过.NET的Proxy,那么你会发现,方法调用和消

ESFramework介绍之(18)―― Tcp用户管理器组件

    当我们的应用中客户端与AS之间是通过Tcp进行通信的时候,通常,应用也要求管理所有在线的用户.这种管理至少包含以下几点:(1) 当用户上线时,记录上线时间(2) 当用户请求服务时,记录请求服务的时间.服务的类型.本次服务下载的数据量(3) 当用户下线时,记录下线时间.并把本次用户登录.请求服务过程中的所有信息持久化保存(如记录到数据库)     在ESFramework中,实现这种管理的是ITcpUserManager组件,通常,该组件由AS使用(因为在AS.FS.IRAS中只有AS是必