65.9K
CodeProject 正在变化。 阅读更多。
Home

在远程服务器上部署.NET胖客户端应用程序集

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.20/5 (7投票s)

2008年3月4日

CPOL

10分钟阅读

viewsIcon

57094

downloadIcon

430

此演示展示了一种将.NET胖客户端应用程序集部署到远程服务器的新方法。

引言

此演示展示了一种将.NET胖客户端应用程序集(如Windows Forms和控制台应用程序)部署到远程服务器的新方法,这使得部署和维护变得更加容易。此演示还着重于.NET应用程序的代码安全性。

背景

在VB时代,我们习惯于准备一个安装文件,并且被迫在每个系统上运行安装程序。

使用.NET,我们确实获得了应用程序发布者和自动更新等选项;但依我看来,这些选项仍然存在以下缺点:

  • 应用程序的大小:如果应用程序太大或太复杂,更新将需要很长时间。
  • 升级应用程序时用户会收到通知:当自动更新发生时,用户可以看到进度,也可能会取消;这可能导致应用程序不稳定。
  • 升级时用户不允许工作:当应用程序通过自动更新进行更新时,用户不允许工作。这可能会延迟用户执行紧急任务。
  • 代码的不安全性:应用程序集物理地位于用户的系统上;特别是使用.NET和JAVA开发的应用程序。任何有权访问用户系统的人都可以使用免费的Reflector工具进入代码。这对于公司来说是不可接受的,因为代码是IT公司的资产。

由于代码不安全,我一直在寻找一种替代的应用程序部署方法,这种方法也易于维护。这种新的应用程序部署方式不应限制使用单层、两层、三层**或**N层架构开发的胖客户端应用程序。此外,为支持多文化和多语言而开发的胖客户端应用程序不应受到限制。简而言之,这种新的应用程序部署方式应允许部署所有类型的.NET胖客户端应用程序。

我了解CITRIX的应用程序部署方式。但是,它需要额外的资金、服务器和维护。因此,小型和中型项目无法承担,也不建议使用。

“ClickOnce”部署

随着.NET 2.0的发布,微软提出了“ClickOnce”部署的概念。“ClickOnce”部署允许开发人员将.NET应用程序集部署到远程服务器(包括HTTP、HTTPS、FTP、共享驱动器等)。

为.NET应用程序创建“ClickOnce”部署后,部署向导会创建“setup.exe”和“.application”文件,除了应用程序文件夹外。“setup.exe”文件负责检查用户系统上特定版本的.NET框架。如果用户系统上未安装.NET框架,“setup.exe”将下载并安装它。“.application”文件只是一个XML文件,其中包含已部署应用程序的详细信息,例如应用程序的部署位置、应用程序名称、版本、公钥等。

用户需要执行“.application”文件才能在用户系统上启动应用程序。“WinFXDocObj.exe”负责读取“.application”文件,将应用程序和依赖文件复制到用户系统,并启动应用程序。“WinFXDocObj.exe”将应用程序和依赖文件从部署位置复制到用户系统上的“C:\Documents and Settings\<user id>\Local Settings\Apps\2.0\<unique folders>\...”;已部署的应用程序将从用户系统上的此路径启动。

如上所述,“ClickOnce”部署仍然存在以下缺点:

  • 应用程序和依赖文件仍被复制到用户本地系统上的唯一文件夹中;这存在代码安全风险。
  • 如果应用程序有新版本,新版本会被复制到一个新的唯一文件夹中;而不是复制或覆盖到现有唯一文件夹中。这会在用户系统上创建相同应用程序的多个版本,并占用更多的物理硬盘空间。
  • 由于唯一文件夹是在每个用户文件夹下创建的,如果同一系统有多个用户,则应用程序会为每个用户部署,这会占用更多的物理硬盘空间。

要求

我想要一种满足以下功能的应用程序部署方法:

  • 应该非常简单易行
  • 应该易于维护
  • 应该允许所有类型的胖客户端应用程序
  • 应该允许在不通知用户的情况下升级应用程序
  • 应该具有最大的代码安全性
  • 用户应该始终使用最新版本的应用程序

我所说的“最大代码安全性”是指应用程序集及其依赖程序集应该在用户系统上虚拟运行。这意味着应用程序集及其依赖程序集应该在应用程序运行期间存在于用户系统上。一旦用户关闭或终止应用程序,所有依赖程序集和应用程序集本身都应该从用户系统上删除。而这正是这种新应用程序部署方法最大的难点。

幸运的是,我找到了解决这个难题的方法,这也是本文的全部内容。

运行演示

  • 将演示应用程序ZIP文件解压缩到一个文件夹中。
  • 打开命令提示符。
  • 将目录更改为解压缩文件夹下的“Client”文件夹。
  • 执行“AppClient.exe /setup”。这将把.OSA文件注册到客户端系统上。一旦.OSA文件注册成功,用户可以通过双击.OSA文件在用户系统上启动“AppClient.exe”。
  • 打开资源管理器。
  • 将目录更改为解压缩文件夹下的“Server”文件夹。
  • 执行“AppSrv.exe”。这将启动应用程序服务器。
  • 将目录更改为解压缩文件夹下的“DemoApps”文件夹。
  • 双击任何一个.OSA文件以启动演示应用程序。
  • demo-win.osa是一个简单的.NET Windows Forms应用程序,没有任何依赖程序集。
  • demo-win-new.osa是一个简单的.NET Windows Forms应用程序,带有一个依赖程序集。

使其工作

我一直对.NET Remoting和Web服务着迷。由于中小型项目确实存在于内网中,我强烈建议使用.NET Remoting。

我正在使用服务器和客户端应用程序的简单实现来满足我的要求。服务器应用程序仍然是普通的.NET Remoting应用程序;但是,客户端应用程序实现了我的想法的所有魔法。

接口 - IApplicationProvider

接口“IApplicationProvider”定义了客户端和服务器应用程序之间通信所需的功能。使用这些功能,客户端应用程序从服务器应用程序获取所需的应用程序集及其依赖程序集。

namespace Company.Common
{
    public interface IApplicationProvider
    {
        bool Ping();

        bool Exists(string application);
        byte[] GetApplication(string application);
        byte[] GetDependency(string application, string dependency);
    }
}

应用程序 - 服务器

由于我们将应用程序集及其依赖程序集部署在内网中的远程服务器上,我们需要某种服务器应用程序,该应用程序负责向客户端应用程序提供应用程序集及其依赖程序集。

为了实现这一点,我创建了一个“Server”应用程序,它使用.NET Remoting架构并实现了“IApplicationProvider”接口。服务器应用程序的配置文件具有自定义标签,用于存储实际应用程序集及其依赖程序集在远程服务器上的设置和物理位置。

<Company.AppSrv>
  <Applications>
    <add name=&quot;WinDemo&quot; location=&quot;.\WinDemo\WinDemo.exe&quot; />
    <add name=&quot;WinDemo_New&quot; location=&quot;.\WinDemoNew\WinDemoNew.exe&quot; />
  </Applications>
</Company.AppSrv>

一旦客户端应用程序向服务器应用程序请求应用程序集或其依赖程序集,在服务器应用程序上实现“IApplicationProvider”接口的类会从配置文件中读取这些详细信息,并将应用程序集或其依赖程序集返回给客户端应用程序。

因此,服务器应用程序的行为与普通的.NET Remoting服务器应用程序完全相同。

应用程序 - 客户端

客户端应用程序负责从服务器应用程序获取特定的应用程序集,并在用户系统上虚拟执行它。这听起来很简单,但在这里实现了在用户系统上虚拟运行应用程序集的魔力。

由于客户端应用程序应该执行远程服务器上可用的任何应用程序集,而不是在客户端应用程序中硬编码特定的应用程序详细信息,我使用了XML文件格式作为特定应用程序配置文件,并将这些特定应用程序文件命名为OSA文件。

配置文件 (.OSA) 需要在用户系统上注册。要在用户系统上注册配置文件 (.OSA),我们需要通过传递“/setup”命令行参数来调用“AppClient.exe”。“AppClient.exe”的“setup”选项使用配置文件的应用程序关联详细信息 (.OSA) 更新用户系统的注册表。一旦配置文件注册成功,用户需要双击.OSA文件来启动应用程序。

private static void Setup()
{
    RegistryKey regKey = null;
    string sAppName = @&quot;Company.AppClient&quot;;

    regKey = Registry.ClassesRoot.CreateSubKey(@&quot;.osa&quot;);
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, sAppName, RegistryValueKind.String);
        regKey.SetValue(&quot;Content Type&quot;, &quot;application/x-osa&quot;, RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(sAppName);
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, &quot;Company Application Client - Runtime&quot;, 
                        RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(string.Format(@&quot;{0}\DefaultIcon&quot;, sAppName));
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, Path.GetFullPath(Application.ExecutablePath) + 
                        &quot;,0&quot;, RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(
             string.Format(@&quot;{0}\shell\open\command&quot;, sAppName));
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, &quot;\&quot;&quot; + Path.GetFullPath(Application.ExecutablePath) + 
                        &quot;\&quot; \&quot;%1\&quot;&quot;, RegistryValueKind.String);
        regKey.Close();
    }
}

为了实现魔术,客户端应用程序将一个OSA文件作为命令行参数获取。客户端应用程序读取此OSA文件,并准备指向已部署特定应用程序的服务器应用程序的URL。借助此URL,客户端应用程序获取“IApplicationProvider”接口的实例,并通过调用底层函数获取特定的应用程序集。客户端应用程序将特定的应用程序集加载到其当前应用程序域中,并调用一个“EntryPoint”函数,该函数在客户端应用程序的内存中启动特定应用程序集的执行。

在演示中,客户端应用程序还能够在调用“EntryPoint”函数时将命令行参数传递给应用程序集。

Assembly asmApp = AppDomain.CurrentDomain.Load(btApp);

if (asmApp.EntryPoint.GetParameters().Length > 0)
{
    asmApp.EntryPoint.Invoke(null, BindingFlags.InvokeMethod, null, 
           new object[] { this._fdOSA.Param.Split(' ') }, null);
}
else
{
    asmApp.EntryPoint.Invoke(null, BindingFlags.InvokeMethod, null, new object[] { }, null);
}

应用程序集所需的每个依赖项都会调用当前应用程序域的“AssemblyResolve”事件。此事件向服务器应用程序请求依赖程序集,将其加载到当前应用程序域中,并返回应用程序集的句柄。

private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
    if (this._fdOSA == null)
    {
        return null;
    }

    string[] sData = args.Name.Split(new char[] { ',' }, 
                     StringSplitOptions.RemoveEmptyEntries);
    byte[] btData = this._appProvider.GetDependency(this._fdOSA.AppName, 
                                                    sData[0].Trim());
    Assembly asmReturn = AppDomain.CurrentDomain.Load(btData);
    return asmReturn;
}

客户端应用程序的实例将一直运行,直到用户关闭应用程序或应用程序集终止。

应用程序配置文件 - OSA

现在,你心中一定会有疑问:为什么配置文件 (.OSA) 要在用户系统上使用和注册?以下是这样做的优点:

  • 用户不应该知道哪个可执行文件将运行应用程序。用户只需双击配置文件 (.OSA),就像我们处理文本文件、Word文档等一样。
  • 当应用程序、配置发生变化,或者将应用程序移动到另一个服务器时,我们只需要更新配置文件 (.OSA) 并将其提供给用户。
  • 更进一步,我们可以在网页上放置指向配置文件 (.OSA) 的链接,而不是将配置文件 (.OSA) 物理地提供给用户。我们可以要求用户浏览此网页并点击配置文件 (.OSA) 的链接来启动应用程序。通过这样做,配置文件也存在于服务器上,因此我们可以修改它而不会打扰用户。我们还可以允许用户从单个网页访问多个应用程序。

简而言之,在应用程序集的重新配置或重新部署过程中,我们没有依赖项。无论何时用户使用配置文件启动应用程序,用户都在使用最新版本的应用程序。而且,所有这些魔法都虚拟发生,因此代码也是安全的。

范围

这是在远程服务器上部署.NET胖客户端应用程序的最简单(像骨架一样)的演示。它需要大量的增强和改进才能满足企业需求,例如:

  • .NET Remoting安全性,以便一组指定的用户和用户组可以访问服务器应用程序。
  • 客户端和服务器应用程序上的正确错误处理。

声明

此演示仅关注.NET胖客户端应用程序。.NET瘦客户端应用程序(如ASP.NET)不能使用这种应用程序部署方法进行部署。

© . All rights reserved.