我的想法是,当客户端主程序加载一个新的 PassiveAddin 时,可以在某个菜单的子Items上添加一项,当双击这个子菜单项时,则弹出该客户端插件提供的“业务操作窗体”。这只是使用客户端插件的可行方式之一,你完全可以根据你的应用来决定使用形式。 IPassiveAddin 接口定义如下:
2 /// IPassiveAddin用于客户端的插件。通常一个PassiveAddin对应着一个服务端的功能插件FunAddin
3 /// zhuweisky2006.03.13
4 /// </summary>
5 public interface IPassiveAddin:IAddin
6 {
7 TypeAddinFormType{ get ;} // AddinFormType必须实现IAddinForm接口
8 }
9
10 public interface IPassiveAddinForm
11 {
12 // PassiveAddin通过IServerAgent发送请求并获取结果
13 void Initialize(IServerAgentserverAgent, string userID);
14 }
IPassiveAddin 直接从 IAddin 继承,仅仅增加了一个属性 AddinFormType ,这个属性就是前面说的客户端插件提供的“业务操作窗体”。“业务操作窗体”必须从 IPassiveAddinForm 接口继承。
“业务操作窗体”只有通过暴露的 Initialize 方法获取 IServerAgent 引用后,才能发送请求获取结果。 Initialize 方法的第二个参数说明当前时哪个用户在操作,这样客户端插件在构建请求消息时,需要将 userID 填充到请求消息的消息头中去,这样服务器才会知道这个消息的来源。
下面的代码说明了客户端主程序是如何加载 IPassiveAddin 的:
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 (IAddinaddin in this .addinManagement.AddinList)
9 {
10 IPassiveAddinpassiveAddin = addin as IPassiveAddin;
11 if (passiveAddin != null )
12 {
13 ToolStripItemitem = 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,EventArgse)
21 {
22 try
23 {
24 ToolStripItemitem = (ToolStripItem)sender;
25 IPassiveAddinpassiveAddin = (IPassiveAddin)item.Tag;
26 FormaddinForm = (Form)Activator.CreateInstance(passiveAddin.AddinFormType);
27 ((IPassiveAddinForm)addinForm).Initialize( this .tcpServerAgent, this .currentUserID);
28 addinForm.Show();
29 }
30 catch (Exceptionee)
31 {
32 ee = ee;
33 }
34 }
上述的介绍没有什么难点,仔细体会一下都能明白,就不多说了。这里我给出一个测试用的功能插件和对应的客户端插件示例。 示例的功能插件用于从 http://www.webservicex.net/globalweather.asmx 通过 WebService 获取城市的天气信息,而客户端插件则用于为用户提供这项服务。
先看服务端功能插件实现:

主要是 DealRequestMessage 方法 的实现,代码非常简单,通过WebService获取指定城市的天气情况,将返回的XML解析封装成IContract,然后返回给客户端。
接下来看客户端插件的实现,分为两步:
首先是“业务操作窗体”界面设计
。
该窗体要从IPassiveAddinForm接口继承。当点击按钮时,处理代码为:

<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--> privatevoidbutton1_Click(objectsender,System.EventArgse)
{
stringcityName=ESFramework.Common.ChsToSpellConverter.Convert(this.comboBox1.SelectedItem.ToString());
WeatherReqContractbody=newWeatherReqContract(this.contractHelper);
body.cityName=cityName;
MessageHeaderheader=newMessageHeader(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.Messagemsg=newESFramework.Network.Message(header,body);
NetMessageresMsg=this.theAgent.CommitRequest(msg,DataPriority.Common,true);
if(resMsg.Header.Result!=ServiceResultType.ServiceSucceed)
{
MessageBox.Show("没有发现对应的服务~!");
return;
}
WeatherPredictionContractresContract=newWeatherPredictionContract(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 实现:

需要格外注意要实现AddinFormType属性,就是前面实现的“业务窗体”类型。
下图是功能服务器加载功能插件的截图:
下图是客户端加载客户插件后的截图:
下图是客户端插件提供服务的截图:
经过上述的介绍,读者应该对开发服务端的功能插件和客户端插件有些了解了。快结束的时候,再为下篇blog开个头。当我们开发了客户端插件和服务端插件后,做调试是一项非常麻烦的工作,因为不仅要启动应用服务器,还要启动客户端主程序、功能服务器才行。为了简化这个过程,我实现了一个Bridge应用程序,只需要加载一pair插件(服务端插件和对应的客户插件),即可进行两个插件的调试,而不用在启动客户端、AS、FS了。
感谢关注!
上一篇:
ESFramework介绍之(7)-- 服务器代理IServerAgent
转到:
ESFramework 可复用的通信框架(序)