默认情况下,首次运行 ClickOnce 应用程序时,会下载该应用程序中包含的所有程序集。但是一些特殊的场景我们可能不希望它这么干,而是希望按照一定规测或者需要用到某个组件的时候再下载。
比如我们可能会有下面一些应用场景的需求:
1、我开发的这个客户端程序是要收费的。但是免费用户也可以使用部分功能。我在技术实现上把收费用户使用的功能封装到了A.dll 组件了,我希望免费用户根本无法获得A.dll,只有收费用户才能获得A.dll,并加载A.dll中的收费功能。
2、我整个应用程序非常大,我不希望我每次更新,用户都需要把所有应用程序都下载下来,用户应该只需要下载他用到的功能组件。
下面我们就来实现一个简单的按需下载的例子,我们完全可以在这个例子基础上,实现上述提到的应用场景1。
演示步骤:
一、新建一个 类库 Project
定义类库输出应用程序名为:OnDemandAssembly,即这个类库编译后产生的文件名为 OnDemandAssembly.dll
这个类库中有如下代码,我们用这段代码来模拟上面提到场景中的一些需要按需加载的功能或者是收费的功能,我们将在主程序中调用这个功能:
using System;
namespace OnDemandAssembly
{
public class DynamicClass
{
public string Message
{
get
{
return "郭红俊测试ClickOnce按需加载功能。";
}
}
}
}
二、新建一个 Window 应用程序
我们将在这个Window 应用程序中加载上述类库。
为了便于我们演示这个程序,请确保这个Window程序具备以下功能:
1、有一个 textbox 控件,这个控件在 Window 的OnLoad 事件中,把应用程序的目录显示在这里,方便我们去监控是否上述 OnDemandAssembly.dll 组件被加载了。
代码如下:
private void Form1_Load(object sender, EventArgs e)
{
this.textBox1.Text = Application.StartupPath;
}
2、由一个 Button 按钮,点击这个按钮后,我们把 OnDemandAssembly.dll 组件中的 Message 信息显示出来,代码如下:
private void button1_Click(object sender, EventArgs e)
{
DynamicClass o = new DynamicClass();
MessageBox.Show(o.Message);
}
Window程序关于ClickOnce设置比较特殊的地方:
我们来配置ClickOnce发布的一些特殊参数,确保可以按需下载需要的组件
本文中没有详细描述ClickOnce设置的各个步骤和参数,如果你对ClickOnce不是很熟悉,建议你首先看一些ClickOnce的入门文章再来看本文。
比如:http://blog.oracle.com.cn/155011/viewspace_3603.html 这里提供的ClickOnce 文章(这里是提供了一个word压缩文件下载,文章在压缩的Word文件中)
选择我们的Window程序,在右键菜单中选择属性,在属性页中选择发布(Publish)标签页。
然后点击 Application File 按钮,我们来设置,需要发布的文件。如下图:
上述按钮打开的窗口如下:
我们在打开的窗口中,设置 OnDemandAssembly.dll 文件的 发布状态为 Include,
并在 Download Group 中为OnDemandAssembly.dll 文件新建的一个下载组,我们这里把这个新的下载组命名为 DemandAssembly01 。
说明:
默认情况下,我们用ClickOnce 第一次安装,或者升级程序的时候,系统只下载 Required 组的文件,其他组的文件系统不下载,需要我们自己编码来下载。
其他ClickOnce的设置跟我们平常使用的时候一样来设置。本文忽略这部分。
我们这时候发布这个程序,我们下载安装后,就会发现OnDemandAssembly.dll 文件不在安装目录下,点击这个程序的按钮,就会报异常,找不到需要的应用程序集
OnDemandAssembly.dll 组件。
下面我们来编码实现,如果应用程序找不到 OnDemandAssembly.dll 组件,就从网上下载这个组件的功能
1、窗口的构造函数中增加 AssemblyResolve 事件的处理逻辑
public Form1()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
}
说明:AppDomain.AssemblyResolve 事件 在对程序集的解析失败时发生。
2、下面我们编码实现 AssemblyResolve 事件的处理逻辑
using System.Reflection;
using System.Deployment.Application;
// 如果我们有多个文件都需要按需下载的话,每个文件影射到那个下载分组,就是这个实体来记录的
Dictionary<String, String> DllMapping = new Dictionary<String, String>();
public Form1()
{
InitializeComponent();
DllMapping["OnDemandAssembly"] = "DemandAssembly01";
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly newAssembly = null;
// 是 ClickOnce 部署方式
if (ApplicationDeployment.IsNetworkDeployed)
{
ApplicationDeployment deploy = ApplicationDeployment.CurrentDeployment;
// Get the DLL name from the Name argument.
string[] nameParts = args.Name.Split(',');
string dllName = nameParts[0];
string downloadGroupName = DllMapping[dllName];
// 下载所需要的文件
try
{
deploy.DownloadFileGroup(downloadGroupName);
}
catch (DeploymentException de)
{
MessageBox.Show("Downloading file group failed. Group name: " + downloadGroupName + "; DLL name: " + args.Name);
throw (de);
}
// 加载组件到应用程序集
// Load the assembly.
// Assembly.Load() doesn't work here, as the previous failure to load the assembly
// is cached by the CLR. LoadFrom() is not recommended. Use LoadFile() instead.
try
{
newAssembly = Assembly.LoadFile(Application.StartupPath + @"\" + dllName + ".dll");
}
catch (Exception e)
{
throw (e);
}
}
else
{
//Major error - not running under ClickOnce, but missing assembly. Don't know how to recover.
throw (new Exception("Cannot load assemblies dynamically - application is not deployed using ClickOnce."));
}
return (newAssembly);
}
一些问题说明:
Q:上述代码中,如果我们这个组件OnDemandAssembly.dll 有最新版本了,并且发布了,但是客户端还是一个老的版本的话,这个逻辑我们没有处理呀?
A:问题这个问题,就是代表你对 ClickOnce 的原理还是不懂,OnDemandAssembly.dll 有最新版本,那你就必须再重新发布一个版本的ClickOnce代码,这时候发布的版本号就不一样了。客户端下不同版本号的ClickOnce 程序是存在不同目录下的(当然服务器也是一样)。
系统检查到你的新版本程序发布后,会为新的版本号建立目录,这个目录下如果你没有用过OnDemandAssembly.dll 的功能,是不会有OnDemandAssembly.dll 组件的,继而上述问题是可以不用考虑的。