嵌入式用户控件:重访






4.83/5 (18投票s)
2006年12月18日
6分钟阅读

197377

1458
使用 VS2005 SP1 可以轻松地将 UserControls 嵌入到 .NET 程序集中
引言
Visual Studio 2005 Service Pack 1 恢复了对 ASP.NET Web 项目的支持,使其能够将 ASPX 和 ASCX 页面编译到单个程序集中。结合设计时所见即所得的支持和巧妙的资源嵌入用法,可以利用 ASP.NET UserControl
技术创建与一流自定义控件一样灵活和强大的 Web 控件库。
背景
Visual Studio 2005 的初始版本取消了对“Web 项目”的支持,这让 Web 开发人员非常失望。作为回应,出现了一个 Web 应用程序项目选项来恢复此功能,但您必须单独下载并安装它。随着 Service Pack 1 的发布,此功能已恢复,现在我们将对其进行试用!
必备组件
项目设置
为了演示嵌入式控件的功能,我们需要创建两个项目
- 一个控件库
- 一个测试网站
您可以使用此项目附带的项目存根,或者如果您愿意,也可以从头开始设置自己的项目。完成后,您的解决方案布局应有一个用于测试的 Web 项目和一个将包含您的嵌入式控件的 Web 项目。
要从头开始,请创建一个新的 ASP.NET Web 应用程序项目。请注意此处区别 - 我们选择“新建”>“项目”。
由于我们正在创建一个程序集,请删除 Default.aspx 和 web.config。这将使您得到一个空项目。
创建 VirtualPathProvider.cs 或从示例项目中复制它。
using System.Web.Hosting;
using System.Web.Caching;
using System.Collections;
using System;
using System.IO;
using System.Web;
using System.Reflection;
namespace ControlLibrary
{
public class AssemblyResourceProvider : VirtualPathProvider
{
string mResourcePrefix;
public AssemblyResourceProvider()
:this("EmbeddedWebResource")
{
}
public AssemblyResourceProvider(string prefix)
{
mResourcePrefix = prefix;
}
private bool IsAppResourcePath(string virtualPath)
{
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/"+mResourcePrefix+"/",
StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) ||
base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsAppResourcePath(virtualPath))
return new AssemblyResourceVirtualFile(virtualPath);
else
return base.GetFile(virtualPath);
}
public override CacheDependency
GetCacheDependency(string virtualPath,
IEnumerable virtualPathDependencies,
DateTime utcStart)
{
if (IsAppResourcePath(virtualPath))
return null;
else
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
}
}
class AssemblyResourceVirtualFile : VirtualFile
{
string path;
public AssemblyResourceVirtualFile(string virtualPath)
: base(virtualPath)
{
path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override Stream Open()
{
string[] parts = path.Split('/');
string assemblyName = parts[2];
string resourceName = parts[3];
assemblyName = Path.Combine(HttpRuntime.BinDirectory,
assemblyName);
Assembly assembly = Assembly.LoadFile(assemblyName);
if (assembly == null) throw new Exception("Failed to load " +
assemblyName);
Stream s = assembly.GetManifestResourceStream(resourceName);
if (s == null) throw new Exception("Failed to load " +
resourceName);
return s;
}
}
}
关注点
- 请注意,构造函数接受一个可配置的前缀。这与原始代码不同。
- 请注意,如果找不到程序集或资源名称,则会抛出异常。这与原始代码不同。
创建或复制 EmbeddedUserControlLoader.cs 类。这是为我们提供 VS2005 工具箱支持的类。
using System.Web.UI.WebControls;
using System.Web.UI;
using System;
using System.ComponentModel;
using System.Web;
namespace ControlLibrary
{
[ToolboxData("<{0}:EmbeddedUserControlLoader runat=server>
</{0}:EmbeddedUserControlLoader>")]
public class EmbeddedUserControlLoader : WebControl
{
# region VirtualPathProvider setup code
static EmbeddedUserControlLoader()
{
if (!IsDesignMode)
{
System.Web.Hosting.HostingEnvironment.
RegisterVirtualPathProvider(
new AssemblyResourceProvider(ResourcePrefix));
}
}
static bool IsDesignMode
{
get
{
return HttpContext.Current == null;
}
}
static string mResourcePrefix = "EmbeddedWebResource";
public static string ResourcePrefix
{
get
{
return mResourcePrefix;
}
set
{
mResourcePrefix = value;
}
}
#endregion
#region Toolbox properties
private string mAssemblyName = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string AssemblyName
{
get { return mAssemblyName; }
set { mAssemblyName = value; }
}
private string mControlNamespace = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string ControlNamespace
{
get { return mControlNamespace; }
set { mControlNamespace = value; }
}
private string mControlClassName = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string ControlClassName
{
get { return mControlClassName; }
set { mControlClassName = value; }
}
#endregion
#region Private members
string Path
{
get
{
return String.Format("/{0}/{1}.dll/{2}.{3}.ascx",
ResourcePrefix, AssemblyName, ControlNamespace,
ControlClassName);
}
}
Control c;
protected override void OnInit(EventArgs e)
{
c = Page.LoadControl(Path);
Controls.Add(c);
base.OnInit(e);
}
protected override void RenderContents(HtmlTextWriter output)
{
if (IsDesignMode)
{
output.Write(Path);
return;
}
base.RenderContents(output);
}
#endregion
#region Helper members to access UserControl properties
public void SetControlProperty(string propName, object value)
{
c.GetType().GetProperty(propName).SetValue(c, value, null);
}
public object GetControlProperty(string propName)
{
return c.GetType().GetProperty(propName).GetValue(c, null);
}
#endregion
}
}
关注点
- 静态方法
虚拟路径的注册有一个特殊的顺序与之相关。Microsoft 建议“在 Web 应用程序执行任何页面解析或编译之前”注册所有虚拟路径,并且稍后注册它们不会影响已编译和缓存的页面。在 Web 门户设置中,您并不总是有权访问
Application_Start()
。因此,我更喜欢一种替代方法,即在用户代码中初始化提供程序。在我测试时,在EmbeddedUserControlLoader
的static
构造函数中注册没有问题,因为这会在该类的任何实例执行之前执行,因此虚拟路径会及时配置,以便OnInit
可以使用它。有关更多信息,请参阅 MSDN 上的 VirtualPathProvider。 - 静态前缀属性
您的控件库必须实现一个唯一的前缀才能使虚拟路径正常工作。此属性允许您在运行时配置前缀。
Get
/Set
方法此类允许您
get
和set
底层控件的属性。稍后将详细介绍。- 工具箱设计时属性
您可以指定要在设计时加载的程序集名称、命名空间和控件名称。稍后将详细介绍。
最后一步是创建您的测试网站。这可以是新的 ASP.NET Web 项目,也可以是传统的 VS2005 网站项目。保留 default.aspx 和 web.config,并确保添加对 ControlLibrary
程序集的引用。
创建和嵌入您的第一个 UserControl
有了解决方案存根,我们就可以开始工作了!让我们创建一个“Hello World”UserControl
来进行演示。
在您的 ControlLibrary
项目中创建一个新的 UserControl
(命名为 HelloControl
)。在 UserControl
中放入一些文本。
然后 - 这是关键步骤 - 将 ASCX 文件的 Build Action
设置为 Embedded Resource
。就这样!您已完成。编译。您的 ASCX 文件现在已嵌入 ControlLibrary.dll 中。
现在转到您已包含 ControlLibrary
引用的测试站点。打开 Default.aspx,然后将 EmbeddedUserControlLoader
控件从 VS2005 工具箱拖到您的页面上。您可以查看 ControlLibrary
中的 HelloControl.ascx.cs 来获取 AssemblyName
、ControlClassName
和 ControlNamespace
的适当值。更改这些值时,控件的设计时表示将显示它打算加载的嵌入式资源的路径。
运行 Default.aspx ,然后惊叹不已。您的用户控件直接从 ControlLibrary
程序集中无缝加载!
既然您已经完成了一些基本操作,那么让我们来看一些更复杂的场景。
高级技术:嵌套控件
您的嵌入式 UserControls
可以嵌套。这工作得非常好,我将留给您自己去尝试。只需将 EmbeddedUserControlLoader
对象拖放到您在 ControlLibrary
项目中创建的 UserControls
中即可。您可以随意嵌套控件。
高级技术:属性访问器
您可以使用 EmbeddedUserControlLoader
来访问 UserControls
的属性。这值得详细理解,因为它是使您的控件感觉像真正的自定义控件的唯一因素。
让我们创建一个反馈控件。我们想收集一些基本信息并通过属性公开。
<%@ Control Language="C#" AutoEventWireup="true"
Codebehind="FeedbackControl.ascx.cs"
Inherits="ControlLibrary.FeedbackControl" %>
<h1>
Your Feedback is <span style="font-size: 5pt">not</span> Valuable</h1>
Name:
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><br />
Subject:
<asp:TextBox ID="txtSubject" runat="server"></asp:TextBox><br />
Message:<br />
<asp:TextBox ID="txtMessage" runat="server" Height="256px"
TextMode="MultiLine" Width="353px"></asp:TextBox>
FeedbackControl.ascx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace ControlLibrary
{
public partial class FeedbackControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public string Name
{
get
{
return this.txtName.Text;
}
}
public string Subject
{
get
{
return this.txtSubject.Text;
}
}
public string Message
{
get
{
return this.txtMessage.Text;
}
}
}
}
现在,让我们创建一个反馈表单来收集信息。我们将不发送电子邮件,而是在回发时将其显示给用户。
Feedback.aspx
<%@ Page Language="C#" AutoEventWireup="true"
Codebehind="Feedback.aspx.cs" Inherits="DemoSite.Feedback" %>
<%@ Register Assembly="ControlLibrary" Namespace="ControlLibrary"
TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:EmbeddedUserControlLoader ID="EmbeddedUserControlLoader1"
runat="server" AssemblyName="ControlLibrary"
ControlClassName="FeedbackControl"
ControlNamespace="ControlLibrary">
</cc1:EmbeddedUserControlLoader>
<br />
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
Text="Submit Feedback" /><br />
<br />
<asp:Panel ID="Panel1" runat="server" Height="244px"
Visible="False" Width="383px">
Name:
<asp:Label ID="lblName" runat="server" Text="Label">
</asp:Label><br />
Subject:
<asp:Label ID="lblSubject" runat="server" Text="Label">
</asp:Label><br />
Message:<br />
<asp:Label ID="lblMessage" runat="server" Text="Label">
</asp:Label></asp:Panel>
</div>
</form>
</body>
</html>
Feedback.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace DemoSite
{
public partial class Feedback : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
this.Panel1.Visible = true;
this.lblName.Text =
this.EmbeddedUserControlLoader1.GetControlProperty("Name")
as string;
this.lblSubject.Text=
this.EmbeddedUserControlLoader1.GetControlProperty("Subject")
as string;
this.lblMessage.Text =
this.EmbeddedUserControlLoader1.GetControlProperty("Message")
as string;
}
}
}
就是这样。您几乎可以像处理自定义控件一样轻松地访问了您的属性。
高级技术:自定义控件包装器
好的,有些人可能对 EmbeddedUserControlLoader.GetPropertyValue()
不满意,我可以理解。在某些情况下,例如商业分发您的控件时,您希望在 Visual Studio 2005 中获得完整的AppDesigner支持。
我们将通过在 ControlLibrary
项目中创建一个轻量级的自定义控件包装器来处理这个问题。创建一个自定义控件 FeedbackControlWrapper.cs。
using System.Web.UI;
namespace ControlLibrary
{
[ToolboxData("<{0}:FeedbackControlWrapper runat=server>
</{0}:FeedbackControlWrapper>")]
public class FeedbackControlWrapper : EmbeddedUserControlLoader
{
public FeedbackControlWrapper()
{
ControlClassName = "FeedbackControl";
AssemblyName = "ControlLibrary";
ControlNamespace = "ControlLibrary";
}
public string Name
{
get
{
return GetControlProperty("Name") as string;
}
}
public string Subject
{
get
{
return GetControlProperty("Subject") as string;
}
}
public string Message
{
get
{
return GetControlProperty("Message") as string;
}
}
}
}
关注点
- 该包装器是一个真正的自定义控件,它使用上面在 Feedback.aspx.cs 中显示的相同的属性访问器技术,但将其隐藏在控件的最终用户面前。请注意,
ToolboxData
控件前缀是FeedbackControlWrapper
。它应该与类名匹配,并且应与UserControl
类名不同。(一种替代命名约定是为包装器名称保存SomeControl
名称模式,并为底层UserControls
使用不同的模式,以免其他人看到)。 - 创建可接受的控件设计时呈现超出了本文的范围,但
EmbeddedUserControlLoader
展示了您需要做什么才能创建设计时呈现。通常,HttpContext
不可用,必须对其进行模拟。任何商业控件都必须比我在本文中做得更好地处理设计时呈现问题! - 您可以向包装器添加属性,这些属性将出现在 VS2005 属性设计器框中。搜索您最喜欢的关于如何在设计时公开自定义控件属性的文章。
新的自定义控件反馈页面与之前的示例几乎相同。但是这一次,我们从 VS2005 工具箱中拖放我们的自定义控件,并将代码隐藏更改为更熟悉的内容。
FeedbackCustomControl.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace DemoSite
{
public partial class FeedbackCustomControl : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
this.Panel1.Visible = true;
this.lblName.Text = this.FeedbackControlWrapper1.Name;
this.lblSubject.Text = this.FeedbackControlWrapper1.Subject;
this.lblMessage.Text = this.FeedbackControlWrapper1.Message;
}
}
}
成功,并且架构更加专业!
高级主题:迁移 VS2005 UserControls
希望迁移 UserControl
文件的用户应注意 @Control
属性中 CodeBehind
和 CodeFile
之间的区别。CodeBehind
是正确的属性,但 CodeFile
是所有在 SP1 之前的 VS2005 中创建的 UserControls
使用的。
请参阅 http://msdn2.microsoft.com/en-us/library/d19c0t4b.aspx 上的更多详细信息
历史
- 2006 年 12 月 17 日 - 初稿
- 2007 年 1 月 7 日 - 通过各种用户评论得到改进
致谢
本文受到了以下来源的启发
- 从嵌入式资源加载 WebForms 和 UserControls
- Visual Studio 2005 Web 应用程序项目选项
- Visual Studio 2005 Service Pack 1
- Paul Wilson 关于 SP1 的博客文章
关于 Benjamin Allfree
Benjamin Allfree 经营着 Launchpoint Software Inc.,这是一家技术无关的公司,专门提供无与伦比的定制软件解决方案。Benjamin 大部分时间都在研究架构和新技术。