智能客户端SmartClient (三)
三、创建程序
客户端应用程序的执行步骤:
1、 登录系统。通过输入用户名和密码或其他任何方式进行系统登录,通常需要一个登录窗体。(该步操作需要客户端应用程序在线执行,因此可以考虑在窗体上提供一个离线工作的按钮,然后直接使用上次在线登录时服务器传递的配置文件副本。这样做会引发一些安全性问题和客户端数据和服务器数据同步的问题。)
2、 由应用服务器验证通过以后,根据系统设定的用户权限获取用户可以加载的应用组件及其相应的配置信息,同时从数据库中获取用户的个性化配置信息,将这些配置信息组合成一个XML配置文件流传递给客户端应用程序。该步操作通常由一个Web服务调用来执行。
3、 客户端程序解析该配置文件流,根据个性化配置信息设置主窗体样式,根据应用组件配置信息加载相应的程序集文件。保存该配置文件以备离线工作时使用。
4、 (本地运行方式)启动后台更新线程,该线程定时搜索更新服务器上是否有最新版本的程序,如果有则自动下载,并提示用户更新程序。
5、 用户通过点击相应的应用组件的按钮或菜单,在客户端主窗体中将显示应用组件自身携带的用户界面。
客户端应用程序基本包含了登录窗体、主窗体和应用组件加载器。加载器内有一个应用组件集合类,同时还提供一些方法用于主程序和应用组件之间以及不同应用组件之间的通讯,某些应用组件是作为公共组件为其他应用提供数据,因此应该通过统一的方法来获取数据。
注意事项和建议:
1、对于网络运行方式,程序集在动态加载的过程从服务器被下载到本地缓存中,而本地运行方式的下载过程由更新组件执行,加载过程则客户端主窗体执行。
在.net中动态加载程序集的方法:
using System.Reflection;
//网络运行方式,加载的程序集路径为服务器上的url地址
assemblyFileName = "http://SmartClient/AppComponent1.dll";
//本地运行方式,加载的程序集路径为本地的目录路径
assemblyFileName = @"c:\SmartClient\AppComponent1.dll";
Assembly asm = Assembly.LoadFrom(assemblyFileName);
2、客户端应用程序的主窗体作为应用组件的容器,可以通过两种方法来显示应用组件的用户界面:
第一种方式是将客户端应用程序设计为多文档界面(MDI)应用程序,每个应用组件都包含有各自的启动主窗体,客户端应用程序执行每个应用组件时,将构造并显示该组件的启动窗体类型。MDI 应用程序中常有包含子菜单的"窗口"菜单项,用于在主窗口或应用之间进行切换。
另一种方式是在客户端应用程序的主窗体界面中预留一块应用界面区域,而每个应用组件作为一个用户控件在执行时被添加到应用界面区域的控件数组中。应用界面区域通常是一个面板(Pane)控件,通过更改用户控件的Z顺序在各个应用之间进行切换(Control. BringToFront())。
3、在客户端应用程序根据配置信息加载相应的应用组件时,同时需要在客户端主窗体的工具栏或菜单上创建对应的按钮和菜单项,并添加事件以便于用户操作。客户端应用程序和应用组件之间应尽量保持独立性,减少关联,以体现客户端应用程序结构清晰和简单。可以通过.net中的回调(事件委托)等方法实现这一点。
4、.net由于缓存的原因,在加载程序集时会出现以下问题:
//第一次加载
Assembly asm = Assembly.LoadFrom (@"c:\SmartClient\AppComponent1.dll");
Activator.CreateInstanceFrom(asm.GetType("AppComponent1.MainForm"));
//第二次加载一个不同目录下的文件名称相同但内容不同的程序集
Assembly asm = Assembly.LoadFrom (@"c:\test\AppComponent1.dll");
Activator.CreateInstanceFrom(asm.GetType("AppComponent1.MainForm"));
第二次加载后,没有正确的构造出不同目录下的程序集中的窗体类型,而是使用了缓存中的第一次加载时的构造类型。
为了解决上述问题,必须在不同的应用程序域(Domain)中构造这两个类型实例。
//第二次加载一个不同目录下的文件名称相同但内容不同的程序集
AppDomain newDomain = AppDomain.CreateDomain("newDomain");
newDomain.CreateInstanceFromAndUnwrap(@"c:\test\AppComponent1.dll", "AppComponent1.MainForm");
使用此方法时需要注意,如果您构造的类型实例包含一个带参数的构造函数,则这些参数对象必须是可跨应用程序域访问的对象,即是可序列化的对象。可以通过继承MarshalByRefObject对象或使用[Serializable]特性标记类,同时设置远程对象的生存期策略。
示例:
public class ParameterClass : MarshalByRefObject
{
public override Object InitializeLifetimeService()
{
//定义远程处理生存期服务所使用的生存期租约对象
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
//设置租约的初始时间。该租约将永远不会到期,并且与之关联的对象将具有无限长的生存期。
lease.InitialLeaseTime =TimeSpan.Zero;
}
}
}