大家都知道能够通过IIS和IE浏览器来直接启动.Net Winform程序或者调用.Net程序集,这就是著名的RichClient技术。 这项技术能够将winform程序的部署维护成本降低到接近传统web方式的水平,而几乎不影响winform程序的编程方式和使用感受。
说来容易,做起来总是会碰到些问题,下文中我们就以一个实际的例子讨论一下如何实现一个只需一次部署就可以终身运行的基于RichClient技术的系统。这个思路已经在本人设计的某广域信息系统中使用,该系统包含几百平方公里内的几百个部署点,已经经过了十余次大大小小的升级,包括对整个界面风格的完全修改,系统均能完成自动部署,几乎不需要额外的维护部署成本。
我们使用一个clientStarter.exe程序从指定的IIS服务器加载启动程序集,这个程序在第一次部署时,随.Net Frame Work、水晶报表等支持环境一起部署,同时部署的还有一个配置文件,里面存放着系统配置服务器的地址。系统配置服务器是系统的核心,它负责存放系统的所有配置文件,这些配置文件不是.Net程序集,不能从IIS服务器上更新到客户端,把配置文件集中摆放在系统配置服务器上,不但便于维护修改,而且还安全可靠。clientStarter程序启动时调用系统配置服务获得系统启动模块的地址,之后就加载模块启动系统。下面是这个过程的时序图:
系统配置服务器通过.Net Remoting和其他程序通信。以下是系统的部署图:
整个过程是很简单的,主要的问题是clientStarter从IIS上加载启动模块所在的程序集后如何启动启动模块呢?
.Net的新特性之一“属性(Attribute)”为这个问题提供了很好的解决方法。我们先声明一个属性CAttbClientStartModual和一个系统启动接口IClientStarter:
整个过程是很简单的,主要的问题是clientStarter从IIS上加载启动模块所在的程序集后如何启动启动模块呢?
.Net的新特性之一“属性(Attribute)”为这个问题提供了很好的解决方法。我们先声明一个属性CAttbClientStartModual和一个系统启动接口IClientStarter:
/** <summary>
/// 带有此属性的类表示该类能够用作客户端启动模块,
/// 该类必须支持IClientStarter接口
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CAttbClientStartModual: System.Attribute
{
public CAttbClientStartModual()
{
}
} public interface IClientStarter
{
void run();
}然后在启动程序集中声明一个启动类CSystemStartModule,该类实现IClientStarter接口,并且标记有CAttbClientStartModual属性:
[CAttbClientStartModual()]
public class CSystemStartModule: IClientStarter
{
public CSystemStartModule()
{
} IClientStarter 成员#region IClientStarter 成员
public void run()
{
MainWnd frm = new MainWnd();
frm.ShowDialog();
frm.Dispose();
} #endregion
}
接下来clientStarter的程序就很简单了,加载程序集后根据属性查找到对应类,创建该类实例然后用IClientStarter接口调用之,IClientStarter接口的run方法调用返回后程序也就结束了。
public class CClientStarter
{
public CClientStarter()
{
//
// TODO: 在此处添加构造函数逻辑
//
} /** <summary>
/// 系统启动入口
/// </summary>
[STAThread]
static void Main()
{
IAppConfigService ifcAppConfig = getAppConfigService(); string sStartURL = ifcAppConfig.getStartURL;
Assembly asb = Assembly.LoadFrom(sStartURL, Assembly.GetExecutingAssembly().Evidence); Type tp = findStartModual(asb);
//创建该类型的实例并且从中查询出IClientStarter接口
IClientStarter ifcStarter = (IClientStarter) System.Activator.CreateInstance(tp); ifcStarter.run();
} /** <summary>
/// 在指定程序集中查找标记有CAttbClientStartModual属性的类型
/// </summary>
private Type findStartType(Assembly asb)
{
System.Type[] arrTypes = asb.GetTypes(); foreach (Type e in arrTypes)
{
object[] arrAttb = e.GetCustomAttributes(typeof(CAttbClientStartModual), true);
if (arrAttb.Length > 0)
{
return e;
}
} throw new ApplicationException("未能在指定程序集中找到启动模块!");
} private IAppConfigService getAppConfigService()
{
根据配置文件获取远程调用对象
.省略
}
}
在用这种思路构件系统的时候必须要注意这些问题:
1. 在部署系统时需要建立新的代码组,赋予来自于应用服务器的地址的程序集足够的权限,否则你会发现你的程序什么都不能干。具体做法参见MSDN
2. 如果你放在IIS服务器上的程序要通过.Net Remoting调用远程对象,那么必须确保客户端本地搜索路径中有该远程对象调用接口的声明,否则远程对象会调用失败。这个问题我也想不通,搜遍国内外各大站点均未得其解。后来只好自己做了个接口文件升级的服务,把新版本的接口文件更新到本地才OK。好在包含有接口声明的程序文件一般很小,所以未造成什么影响。如果有哪位仁兄知道请务必赐教。
3. 确保客户端的IE不在“脱机工作”的状态。否则就像离线浏览网页一样,新版本的程序将不能被更新到客户端。说到这里我不禁要抱怨一下.Net,在低速网络环境中,脱机时系统不只是启动时比联机快的多,而且进入系统后操作反应也是这样。完全搞不懂为什么会这样,难道每次引用到一个在IIS上的程序集中的类型时系统都会去检查IIS上的程序集版本吗?这样毫无用处,因为所有的类型均是在一个APP Domain中加载的,就算有新版本的程序集,也必须要退出系统重启动才行的呀!真是不明白!
4. 客户端最好不要用win98系统,因为总是会出些莫名其妙的问题,特别是使用了水晶报表之后。另外最好也不要用winform下的水晶报表,部署复杂,运行不稳定,毛病一大堆。推荐微软新出的reporting service. 真是绝妙的好东东。
















