原作者:John Dundon

原文地址:​​http://blogs.msdn.com/b/webdevtools/archive/2010/06/10/web-custom-control-behavior-and-authoring.aspx​

原文题目:Web Custom Control Behavior and Authoring

针对 Visual Studio 的 Web 自定义控件的最佳实践和建议


这个帖子的目标是为使用 Visual Studio 开发自定义控件的供应商提供一些建议,文章将深入介绍在执行一些常见操作的时候, Visual Studio  如何处理,以及一些优化客户体验的建议,主要集中在  VS2008 和  VS2010,这篇文章不会覆盖运行时或者控件编程的内容,例如任何控件的使用,或者设计时期的特定 API 等等,关于其他方面的更多信息,可以参考后面的链接与资源。


这篇文章分成以下几个部分 

  • 回顾 WSP 和 WAP 的编译模式
  • 工具箱和 Visual Studio 如何引用你的控件程序集
  • 安装控件到工具箱的基本技术
  • 工具箱中显示那些控件
  • 拖/放控件和引用是如何加入的
  • 安装控件的其他秘诀
  • 在添加引用对话框中显示
  • 加入到   AssemblyFoldersEx
  • 最佳实践的基本总结
  • 链接和资源


回顾 WSP 和 WAP 的编译模式


为了理解在 Web 应用程序项目和 Web 网站项目中如何引用自定义控件的错综复杂的关系,非常重要的一点就是将两种项目的区别列出来。

在 Web 网站项目中,网站作为一个文件夹出现,但是没有通常的项目文件,网站的引用在配置文件中以条目的形式声明,程序集保存在 bin 文件夹下,当生成一个网站项目的时候,Visual Studio 实际上在 VS 进程的一个独立的应用程序域中调用 asp.net 编译器,然后报告编译器返回的错误信息,同样,对于任何打开的页面,Visual Studio 将自动的在后台运行 ASP.NET 编译器,网站项目可以在页面中使用 CodeFile 指令,这个指令表示这个后台代码文件将实际上作为页面的部分类被一起编译,所以,在后台代码文件中没有显式的自定义控件的引用说明。


​ 而对于 Web 应用程序项目来说就不一样了,Web 应用程序项目更像一个类库项目,然后在上面加上一个网站项目,  项目的引用被保存在项目文件的引用列表中,IDE 也使用它来编译你的后台代码文件,所有引用不是在 GAC 中的程序集将被复制到 bin 文件夹中,如果能够推定出来的话,这些程序集所依赖的程序集也将被复制到 bin 文件夹中(至于 Visual Studio 如何推定引用关系,可以参考 ​​Chris Mann 关于这个问题的系列​​ ), 这些程序集随后就可以被 ASP.NET 允许在页面中引用,在运行时使用。引用系统还可以允许 Web 应用程序项目的在线代码提示功能,Visual Studio 还会针对打开的页面运行内部的  ASP.NET 编译器 (与网站项目相同),然后创建你的后台代码的引用,Visual Studio 实际上既不会在 build 中运行 ASP.NET 编译器,也不会因为内联代码的错误阻断生成的过程。在生成项目的时候仅仅运行 IDE 的编译器,这就意味着一些内联代码的错误可能被忽略,Web 应用程序项目继承了类型项目的优点。


​ ​工具箱和引用


安装控件到工具箱

有许多种方式可以安装控件到工具箱中,常见的有:


更多地信息可以查看 MSDN 中的相关信息


工具箱中显示哪些控件


默认情况下,Visual Studio 将会显示与活动的编辑器以及 Framework 版本相应的控件,所以,在编辑 ASPX 文件的时候,你将仅仅会看到 Web 相关的控件,在 Winform 中,你仅仅能看到 Winform 控件,通过在工具箱的右键菜单中选择“显示所有”,你可以看到所有安装在工具箱中控件,但是,只有与活动编辑器以及 Framework 版本相应的控件才是可以使用的。


基于不同的 Framework 显示控件


在 VS2008 和  VS2010 中,工具箱中仅仅显示可能应用于项目针对的 Framework 的控件,Visual Studio 将会遍历控件所依赖的程序集来找出它应用在哪个版本的 Framework 中,是 2.0, 3.0, 3.5 还是 4.0,然后,依赖于你的网站,你将会看到对应于你的  Framework 以及更低版本的控件。

在 VS2008 中,如果你封装和安装了针对不同 Framework 的多个版本的控件,最终的开发者可能因此在工具箱中看到多个不同版本的控件,例如,如果顾客开发的目标是 Framework 3.5,而你提供了 Framework 2.0 和 Framework 3.5 的控件,那么,他们将会在 Framework 3.5 的项目中同时看到 Framework 2.0 的控件。实际上,在 VS2010 中,提供了一个特征,如果你的控件有多个针对不同 Framework 的版本,而且命名是相同的,仅仅匹配的最高版本的控件会显示在工具箱中,所以,如果你封装和安装了 2.0,3.5 和 4.0 的控件,而且你的网站项目是 4.0 的,你应该只会看到 4.0 版的控件。如果针对不同 Framework, 你的程序集的名字不同,例如名字是:MyControls.Fx20.dll, MyControls.Fx35.dll, 和 MyControls.Fx40.dll,所有的版本都会显示,因为 VS 会将他们看作不同的控件集。所以,在这个例子中,对于 4.0 版的网站项目,所有版本的控件都会显示在工具箱中,在 3.5 版的网站项目中,2.0 和 3.5 版的控件会显示出来。


在 VS2010 中有一个行为特点,提供多个版本的控件供应商可能要变通一下,如果你的控件程序集名字相同但是针对不同的 Framework 版本,当将你的控件加入到 Framework 的 AssemblyFoldersEx 注册表位置的时候,控件的最高版本可能错误的针对低版本启用,为了绕过这个行为,你可以增加一个注册表值来说明每一个程序集所针对的 Framework 版本。


[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\ToolboxControlsInstaller\<strong name of your controls assembly>]
"TargetFramework"=".NETFramework,Version=vx.x"

可以用 2.0,3.5 或者 4.0 取代上面的 x。

例如,如果你的控件使用 Toolbox Control Installer 方法安装,注册表中可能看起来如下所示:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\ToolboxControlsInstaller\MyCompanysControls.Web.UI, Version=2.0.105.0, Culture=neutral, PublicKeyToken=148af42dc4482dc1]

@="MyCompanysControls for ASP.NET Targeting NET 20"
"CodeBase"="C:\\Program Files\\MyCompanysControls\\MyCompanysControls for ASP.NET Targeting NET 20\\MyCompanysControls.Web.UI.dll"
"TargetFramework"=".NETFramework,Version=v2.0"

当然,针对这些例子来说,x64 的操作系统需要使用 SOFTWARE 和 Microsoft 中的  Wow6432Node。

拖动一个控件 - 增加了哪些引用


当从工具箱中拖出一个控件的时候,Visual Studio 努力使控件立即可用,这将包括在项目中增加适当的引用,在你的页面中增加注册指令,这里有一些重要的事情需要考虑,Visual Studio 如何处理,你可以考虑哪些有效的途径来安装你的程序集。

网站项目

在网站项目中,当拖动一个控件到页面上的时候,Visual Studio 增加控件所需要的必须的引用,如果程序集在 GAC,Visual Studio 将在你的 web.config 中增加一个引用,如果程序集在磁盘上,Visual Studio 将复制这个引用的程序集到 bin 文件夹中,这包括包含控件的程序集,以及所依赖的程序集,网站项目通过搜素控件程序集被加入的目录查询并且添加这些程序集,以及已知的 MSBuild 引用程序集方案,更多的关于引用搜索的问题,可以查看 ​​Chirs Mann 的博客 Assembly Resolution in MSBuild​​. 在后面的编译时和运行时都应该工作。

Web 应用程序项目

对于 Web 应用程序项目来说,情况稍有不同,而且更加复杂了,Visual Studio 将复制引用的程序集到你的 bin 文件夹中,包括第一层的引用和依赖的程序集,像在网站项目中一样进行搜索, 如果你的程序集在 GAC 中,引用被加入到 web.config 中,实际上,仅仅第一层的引用被加入到项目的引用列表中,这就意味着你的控件从另外一个依赖的程序集中定义的一个类派生或者一个接口实现,你的网站将不会编译成功,因为项目需要关于引用程序集的信息,用户将会得到一个非常简单的信息来指导他解决问题。


Error 1 The type 'ReferencedAssemly.IExternalInterface' is defined in an assembly that is not referenced. You must add a reference to assembly 'ReferencedAssemly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. C:\Projects\MyProject\Default.aspx.designer.cs 32 56 MyProject


如果你的控件没有类似的继承机制,那么你就不会得到编译错误,同样在运行时刻你应该也不会得到错误信息,因为  ASP.NET 将在 bin 文件夹中查找任何需要的类型,实际上,用户可能在编码阶段得到一个编译错误,针对试图在父程序集中返回一个依赖程序集中类型的情况,

如果在你的模型中,你的控件一部分在一个程序集中,一些在另外一个程序集中,而且这两个程序集之间是依赖的,这种情况就会更加复杂,在这种情况下,当你拖动,然后放置,上面描述的一些行为就会发生,第一层的引用被加入引用列表,两个程序集被复制到 bin 文件夹中,实际上,如果你后来从依赖的程序集中拖放控件, Visual Studio 将不会添加这个程序集的引用,这个行为是由于网站引用系统的设计造成,因为 Visual Studio 认为这个程序集已经被引用了,所以不会加入到引用列表中。

像上面提到的,当你第一次拖动并且放置一个控件的时候,第一层引用被加入到项目文件的引用列表中,在 VS2008 中,这个值收到一个“提示路径”,这意味着在生成,任何提示位置的程序集更新和所有的依赖都将被复制到 bin 文件夹。在 VS2010 中,提示路径不再在第一次拖放的时候自动生成,这意味着在重新生成,或者清理,然后生成,所有依赖的程序集将被删除,而且不需要重新复制到 bin 文件夹,要解决这个问题,MSBuild 引用程序集解析系统需要能够发现他们,增加他们到可搜索路径中,你可以将增加的路径加入到 .NET Framework 的 AssemblyFoldersEx 注册表位置中,关于如何增加这些程序集到 AssemblyFoldersEx 的问题,可以阅读 加入你的控件到 ​AssemblyFoldersEx​ 位置。


安装控件的其他窍门


在添加引用的对话框中显示控件程序集


有许多方法添加你的控件到添加引用对话框中,建议的方法是将你的控件程序集加入到注册表中 AssemblyFoldersEx 中的位置。


将你的控件加入到 AssemblyFoldersEx 位置



[HKEY_CURRENT_USER\SOFTWARE\Microsoft\.NETFramework\<version>\AssemblyFoldersEx\MyAssemblies]@="<AssemblyLocation>"


 <AssemblyLocation> 是你的程序集的安装位置,例如  “C:\Program Files\My Controls\”,  <version> 是 .NET Framework 的版本 (v2.0.20727, or v3.5, or v4.0.xxxxx), 如果 v3.5 注册键不存在,你可以创建一个。


注意,你可以选择仅仅通过将 HKEY_LOCAL_MACHINE  替换为  HKEY_CURRENT_USER 为当前用户做这个设置, 当然在 x64 的环境下,使用 SOFTWARE 下面的 Wow6432Node  节点。


使用适当的 Framework 版本是重要的,但是同样推荐位于  HKLM\SOFTWARE\Microsoft\.NETFramework\ 下面的  AssemblyFolders 键,这将在你针对多个不同的 Framework 创建多个版本的控件时确认你的控件引用正确的版本。


最佳实践的总结


  • 在 Visual Studio 中最简单的开箱即用的控件程序集就是将所有的内容放入一个程序集中。
  • 在 Web 应用程序项目中,Visual Studio 仅仅添加主程序集的引用,而不会添加依赖程序集的引用。
  • 你可以将自定义控件放到分开的程序集中,虽然并不代表有问题,但是必须意识到:
  • 建议不要将控件分别放到主程序集合依赖的程序集中,因为在拖放控件的时候,依赖的程序集可能不会被添加到引用中
  • 如果你的控件继承或者引用一个依赖程序集中类或者接口,在 Web 应用程序项目中,用户在第一次拖放控件的时候,会得到一个编译错误,因为依赖的程序集没有被自动引用到项目中。
  • 特别是在使用依赖程序集的时候,建议增加你的程序集位置到 AssemblyFoldersEx 注册表节点,这将会允许 MSBuild 程序集发现系统知道这些程序集,而且在你的 bin 文件夹因为清理或者重新生成被删除的时候,可以被复制回来。
  • 当针对不同的 Framework 版本发送不同版本的自定义控件的时
  • 确认在拖放的时候正确的程序集被添加,确认增加你的控件程序集的路径到正确的  AssemblyFoldersEx 位置。
  • 确认工具箱中控件与你的网站项目的版本是正确的版本,在 ToolboxControlInstaller 下每一个程序集的 TargetFramework 注册表项也是正确的。

链接和其他资源: 

原作者:John Dundon

原文地址:​​http://blogs.msdn.com/b/webdevtools/archive/2010/06/10/web-custom-control-behavior-and-authoring.aspx​