你的第一个 ASP.NET 自定义控件

分享于 

23分钟阅读

Web开发

  繁體

CustomEmail for Asp.NET

介绍

本文主要针对那些没有 ASP.NET 定制控件经验的开发者。 在本文中,我将解释 behind 自定义控件的概念,并提供一些有用。 源代码中有一些有用的控件,比如电子邮件链接控件,它们生成一个电子邮件链接,显示电子邮件。邮件主题和电子邮件的。 有一个有用的自定义控件,一个有绑定标签的自定义编辑框,一旦单击标签,它就可以访问。 我还将专注于为尚未使用自定义控件的程序员创建自定义控件的好处。

如何使用代码

所有代码都在一个项目库中,因这里你可以编译和重用它,但是网站是一个简单的测试。 这个解决方案是用 Visual Studio 2008和 C# 制作的。

背景

以. NET 程序员的经验,我意识到在 ASP.NET 2发布后,它最小化了创建自定义控件的需求。 ASP.NET 1.0相比,用户控件具有更大的功能。 例如引用用户控件并通过代码( 有一个解决办法) 操作它的状态并在无 Casting ( 也有一个解决办法)的代码中调用智能感知是不容易的。 但更多的是,设计视图非常糟糕,你只看到一个灰色的框,用户控件 with。 我见过一些程序员在 ASP.NET 1.0中做简单的自定义控件,但是 ASP.NET 2.0中看到的大多数控件都是用户控件。 有一天,我对数据可以绑定控件进行了一个快速的技术会话,在会话之后,我们为什么要创建复杂的代码。 在我的工作中,我意识到如何创建自己的自定义控件来解决不能解决的许多问题。 learning learning learning,我发现当我在开发一些AJAX控件时,我会再次拖动到自定义控件中,这样我希望这篇文章能像我期望的那样有用,这对 non-custom-control。

内容

  • 什么是用户控件,什么是自定义控件?
  • 使自定义控件优于用户控件的原因。
  • 工具箱,icon 和标记注册。
  • ViewState循环管理。
  • 简单自定义控件 vs 复合自定义控件。
  • 恢复已经发布的数据。
  • 管理简单事件。
  • 扩展自定义控件。
  • 自定义控件和Web部件。
  • 其他功能。
  • 逐步总结创建你的第一个自定义控件。

什么是用户控件,什么是自定义控件?

用户控件,是标记( HTML和服务器控件)的容器,可以作为代码单元使用;可以添加属性。事件和方法。 自定义控件创建相比,用户控件创建非常简单,通常,通过从工具箱拖放控件来构建。 自定义控件,是扩展 Control 或者 WebControl 类的类。 在使用控件之前,必须对控件进行 register 操作,就像工具箱中的任何. NET 组件一样。

使自定义控件优于用户控件的原因

在所有场景中都不需要创建自定义控件,但是我将使用一些场景,让我们认为自定义控件是第一种选择。

  • 在不同的应用程序。网站甚至外部应用程序之间共享控件,可以使用定制控件,这些控件是非常易于使用和使用的组件。
  • 为安全起见,如果在它的他组织或者节中开发控件,或者控件具有敏感数据,则使用自定义控件。
  • 如果性能和可以伸缩性存在问题,请始终进行自定义控件,只是因为可以控制渲染过程。
  • 丰富的设计支持:如果你想要支持带有丰富工具的设计模式,请继续使用定制控件。 Uou可以在设计时控制 icon。标记前缀注册和生成的标记。 你可以在属性窗口中看到许多内置属性和事件,以及所有自定义属性和事件。 另一方面,用户控件的设计支持非常差,而且属性窗口中没有构建的属性。
  • 要构建你自己的库或者组织可以重用库,我们应该去定制控制解决方案。
  • 如果希望使控件与其他控件交互并在设计模式下完全受支持,应考虑自定义控件。 例如其他控件可以包含自定义控件,而且你可以在设计模式中将页控件拖动为自定义控件,但不能将控件控件拖放到主页面的用户控件。 如果要创建非可视组件并希望在设计时完全支持该组件,请选择自定义控件。
  • 自定义控件是可以移植的,不仅适用于 ASP.NET,而且适用于 SharePoint ( 如果它扩展了 WebPart 类)。 如果你想创建一个可以扩展的web应用程序 Plug-Ins,你将接受程序集中的自定义控件。

工具箱,icon 和标记注册

New Project template

使用 Visual Studio 8 ASP.NET 服务器控件模板创建项目时,它将为你生成以下代码:

[DefaultProperty("Text")][ToolboxData("<{0}:WebCustomControl1 runat="server">")]publicclass WebCustomControl1 : WebControl {
 [Bindable(true)]
 [Category("Appearance")]
 [DefaultValue("")]
 [Localizable(true)]
 publicstring Text {
 get {
 String s = (String)ViewState["Text"];
 return ((s == null)? String.Empty : s);
 }
 set {
 ViewState["Text"] = value;
 }
 }
 protectedoverridevoid RenderContents(HtmlTextWriter output) {
 output.Write(Text);
 }
}

这就是你想要创建基本自定义控件的所有步骤。 它定义了 ToolboxData 属性,该属性控制ASPX页中的注册标记作为服务器标记。 它扩展了 WebControl,它是最抽象的视觉控件。 它生成具有一些修饰属性的属性。 你将看到所有内部属性字段都被 ViewState 对象替换,finally 将该控件呈现为一个简单的文本。 我们可以稍微修改一下生成的代码,以满足我们的需要:

[ToolboxData("<{0}:CustomEditBox runat="""server" Label="[Label]" Text="[Text]">")]

我用自定义元素替换了生成的标记。 注意,我添加了更多的属性来在容器ASPX页面中生成。 对于组件的icon,只需要创建一个与自定义控件( 你可能在同一项目中有其他控件,因此你可能必须创建多个 icon 映像) 相同 NAME的BMP图像。 icon 图像是可选的。

custom control icon

最后,这里 icon 将不会出现在自动注册选项卡中的工具箱中。 应通过选择工具箱项上下文菜单,浏览程序集,然后对组件进行浏览,来对工具箱项进行。 现在,你将在工具箱中看到 icon。

除了下面的代码,几乎一切都准备就绪了:

[ToolboxData("<{0}:CustomEditBox runat="""server" Label="[Label]" Text="[Text]">")]

{0} 如何获取值,从哪里获取? 不要担心它;你可以使用这里属性来 register 自己的TagPrefix:

[assembly: TagPrefix("FirstCustomControl", "FC")]

你应该在你的类库项目中添加这行代码,然后在类库项目中添加该行,你将看到该名称空间的注册,所以该名称空间中的所有类都具有相同的前缀。 然后,每次从工具栏拖动控件到网页时,它都会生成这里元素:

<FC:CustomEmailLinkID="CustomEmailLink1"runat="server"/>

ViewState循环管理

这个主题非常重要,我相信它是自定义控件中最重要的主题。 在视图视图中,有许多程序员在尝试保存子控件或者内部控件时,会出现同样的错误,但是最终会导致错误: "这里 [object] 不可序列化"。 首先,让我们了解一下ViewState是什么。 对象是维护页面状态及其子控件的内部 ASP.NET 服务器对象,因此与保存与特定会话相关的对象的Session 对象很不同。 会话变量可以跨所有页面访问,但是ViewState仅用于页面,并且将作为隐藏字段呈现给客户端。 这也就是为什么 Session 对象不是可以序列化的( 当然,如果是 InProc的话)。 ViewState不是 HttpContext的属性,因为它是单个页面的唯一对象。 现在,让我们快速查看一下这个代码:

[DefaultProperty("Text")][ToolboxData("<{0}:CustomEmailLink runat="server">")]publicclass CustomEmailLink : WebControl {//.... other code//.... other code[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]publicstring Email {
 get {
 String s = (String)ViewState["Email"];
 return ((s == null)? "[Email]" : s);
 }
 set {
 ViewState["Email"] = value;
 }
}

This属性并不保存内部状态 inside,因为在每次回发自定义控件时都会创建自定义控件,因此在服务器中创建该对象并将它的从已经发布的页中恢复为隐藏字段。 让我们来看看下面的代码:

[DefaultProperty("Text")][ToolboxData("<{0}:CustomEditBoxPrimitive runat="""server" 
 Label="[Label]" Text="[Text]">")]publicclass CustomEditBoxPrimitive : WebControl {
 publicstring Text {
 get {
 String s = ViewState["Text"] asstring;
 return ((s == null)? "[Text]" : s);
 }
 set {
 ViewState["Text"] = value;
 }
}

现在,CustomEditBoxPrimitive。我命名that原因,因为它将创建一些我们需要从头开始的功能。 它使用内部 TextBox 控件将 TextBox 呈现为 CustomEditBoxPrimitive的一部分。 但是,我们不在ViewState中保存 TextBox 本身,我们甚至不应该。 我们只保存视图视图中的重要属性,比如 TextBox 中的Text 或者下拉 List的SelectedValue。 根本不需要在视图视图中保存任何复杂对象。 序列化机制的另一个问题是它是一个昂贵的过程,它会影响性能和可伸缩性。 在使用ViewState时,只需保存基本值,比如简单数据类型。

简单自定义控件 vs 复合自定义控件

将所有自定义控件作为复合( 扩展 CompositeControl 类) 控件创建不是一个坏主意。 创建一个简单的定制控件( 扩展 WebControl )的好处是。 在 StringBuilder 变量中构建子控件树并呈现它,要比在每个子控件中创建实例要快得多。 另一方面,复合控件支持在设计时自动呈现,容器行为( 用于实现 INamingContainer ) 为控件和所有子控件创建命名空间。

Custom Email

简单控件( 扩展 WebControl ) 已经有一个" Controls"属性,可以添加子控件,并且已经有一个 CreateChildControls 方法来创建自己的子树。 因此,这两种模型之间的差别并不大,只要你理解 behind的概念。

publicclass CustomEditBoxPrimitive : WebControl {//...... code//..... code[Bindable(true)]protectedoverridevoid CreateChildControls() {
 BuildControl();
 base.CreateChildControls();
}privatevoid RenderDesign(HtmlTextWriter output) {
 output.Write("Text:{0} Label:{1}", this.Text, this.Label);
}privatevoid InitControls() {
 _innerTextControl.EnableViewState = true;
 _innerTextControl.ID = "Inner_TextBox";
 _innerLabel.EnableViewState = true;
 _innerLabel.ID = "Inner_Label";
 LoadDataState();
}

可以看到,控件是 WebControl ( 最抽象的视觉控制) 中的inherit。 同时,它还支持或者有创建子树控件的基础,如 Controls 属性和 CreateChildControls() 虚函数。

恢复已经发布的数据

还原已经发布数据是任何自定义控件中有输入数据的最有趣的部分,可以能是文本或者选定索引。 我在考虑框架如何知道任何呈现给客户端的文本框的更改。 我想可以能是在客户端生成了一个JavaScript来捕获客户端控件所发生的任何更改。 但是,我没有看到这样的东西。 我意识到页面中的任何服务器按钮都已经将数据发布到页面表单中,所有数据都在 HttpRequest 中。 所以,我编写了一个简单的代码来提取控件的发布数据,对我来说很适合。

publicclass CustomEditBoxPrimitive : WebControl {//.... code//.... codeprivatevoid LoadDataState() {
 if (this.Context!= null) {
 foreach (string key inthis.Page.Request.Form.AllKeys) {
 if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
 this.Text = this.Page.Request.Form[key];
 }
 }
 }
}

这里代码仅供教程用途,但确保有它的他有用的代码,我们需要添加一个事件。 获取发布数据的另一种方法是使用框架支持的自定义控件 api,比如实现 IPostBackDataHandler 接口。 它有一个 LoadPostData 方法,可以准备 key-value Collection 中的所有数据,只要准备好使用。

PostBack

[DefaultProperty("Text")][ToolboxData("<{0}:CustomEditBox runat="""server" Label="[Label]" Text="[Text]">")]publicclass CustomEditBox : CompositeControl,IPostBackDataHandler {//... code//.. codepublicbool LoadPostData(string postDataKey, 
 System.Collections.Specialized.NameValueCollection postCollection) {
 if (postDataKey == this.UniqueID) {
 foreach (string key in postCollection.Keys) {
 if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
 this.Text = postCollection[key];
 }
 }
 }
 returnfalse;
}

当我尝试使用它时,这个方法从未触发。 当我意识到对象客户端 NAME 和对象 UniqueID 值之间存在一个关系时,我认为调试配置中出现了错误。 如果 ASP.NET 查找页中任何控件的NAME 值作为自定义控件的UniqueID,它将考虑将发布数据发送到服务器控件。 无论如何,可以添加一个隐藏字段,该字段具有相同的唯一 ID,仅用于解决这里问题:

privatevoid BuildControl() {//.. code//.. code hidControl.Text=String.Format("<input type="hidden" name="{0}"/>", this.UniqueID);
 this.Controls.Add(hidControl);
 ClearChildViewState();
}

用这种方法,你的事件将会被触发,魔法将会发生。 当然,我们可以使自定义控件 NAME 本身与 UniqueID 相同,但是我在本教程中尝试了避免重写 RenderContents 方法,以便只通过 CreateChildControls 方法构建子控件树。

管理简单事件

管理简单事件非常简单简单。 你可以创建自己的自定义事件并引发此事件。 你可以使用以下两种方案之一: 引发与自定义操作相对应的事件,比如在比较文本数据后和发布数据之后引发 OnChanged 事件:

[Bindable(true)][Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]publicstring Text {
 get {
 String s = ViewState["Text"] asstring;
 return ((s == null)? "[Text]" : s);
 }
 set {
 if (ViewState["Text"]!= null && (ViewState["Text"].ToString()!= value))
 if (this.Text!= this._innerTextControl.Text) 
 OnTextChanged(this, EventArgs.Empty);
 ViewState["Text"] = value;
 }
}

或者在它的他场景中挂钩到任何内部控件的内部事件,可以能是内部 LinkButtonOnClicked 或者它的他方案。

扩展自定义控件

定制控件的主要功能之一是扩展它们,或者扩展现有的富控件。 假设你的网站中有一个查找数据,它总是通过自定义数据源进行输入,并以可以枚举的类型启动。 在这种情况下,可以将具有属性的下拉 List 扩展为可以枚举值,并将它的用作 SourceType。 然后,你将使用自己的业务对象来获取数据并填充 List,这个控件非常容易使用。 你可以创建自己的工程库中的库,或者你可以能有一个特殊的日期选择器。 假设你想要更改服务器控件的呈现行为,例如使菜单呈现为嵌套列表,或者将 GridView 作为定位在( 你可以使用CSS适配器进行操作) 控件中的。 扩展控件可以直接从服务器控件继承。

渲染后:

在 Decorator Pattern 中使用修饰器,这意味着从抽象类 WebControl 继承,并从相同的WebControl 引用 Target 控件的实例。

要获得对Decorator的良好文档,请在本文中查看

通过装饰 Pattern 扩展控件超出范围,但可以为特定类型的控件创建扩展程序,如果继承并引用了类型为的控件,则可以为所有可视控件创建扩展程序。 在这种情况下,使用一个魔术类,你可以扩展所有的可视控件。

publicclass LinkedEmailDecoratorExtender:WebControl {
WebControl _baseControl = null;public LinkedEmailDecoratorExtender(WebControl baseControl) {
 _baseControl = baseControl;
}protectedoverridevoid Render(System.Web.UI.HtmlTextWriter writer) {
 writer.Write(" <div {0}="{1}">", "style", 
 "'border: medium dotted #FF00FF; padding: 5px; margin: 5px;" + 
 " width: auto; height: auto; font-family: Arial; font-weight: bold;'");
 _baseControl.RenderControl(writer);
 writer.Write("</div>");
}

自定义控件和Web部件

Web部件是 ASP.NET 2.0中令人惊奇的特性,ASP.NET 2.0网站pat的美妙之处在于与SharePoint的兼容性。 如果从 WebPart 继承自定义控件,可以在 ASP.NET 页面中使用完全支持的页面,并提供令人惊讶的个性化行为。 同时,你可以在SharePoint中重用它,因为它们来自同一个基类。 所有你需要做的只是从 WebPart 中进行 inherit,然后你将逐步构建你的功能。 这不是一个简单的解决方案。 WebParts超出了本文的范围。 但是,我想要 Highlight 扩展 WebPart 是让我们考虑使用自定义控件的原因之一。

其他特性

自定义控件的主题非常广泛,我只解释了非常小的部分。 但是,我们也可以考虑非可视化控件,包括数据源。AJAX组件和AJAX扩展工具工具包。 但是,在学习如何创建和使用自定义控件之前,不建议大大跳动创建AJAX组件。

逐步总结创建你的第一个自定义控件

我们可以总结构建第一个自定义控件( 看看 CustomEditBox.cs ):

  • 使用 ASP.NET 服务器控件模板创建一个新项目。
  • 更新将在设计模式中创建的相应标记的ToolboxData 属性。
  • 在 AssemblyInfo.cs 更新 TagPrefix 以控制 Prefix 注册。
  • 将属性添加到保存和从ViewState对象获取数据的类中。
  • 创建子控件作为类字段。
  • 重写 OnInit 并创建子控件实例,初始化并创建你的事件处理程序。
  • 重写 CreateChildControls 并生成你的子树。
  • 实现 IPostBackDataHandler ;如果要获得发布的数据,请不要忘记使任何渲染控件 NAME 相同的UniqueID
  • 如果希望在设计模式中以不同于运行库的方式呈现控件,请将 DesignMode 属性应用于两个模式之间的switch。
  • 如果要直接扩展控件以添加有限的功能,请使用继承。 如果要为广泛的控件添加行为,请使用装饰器 Pattern。

我希望你喜欢这篇文章和代码。


相关文章