### ASP.NET 重用代码技术 - 用户控件技术

在前一篇文章当中，我们一同探讨了如何使用 ASP.NET 中的代码绑定技术来使得代码重用变得简单可行。我们发现，利用代码绑定技术我们可以容易的将我们的代码和内容分离开来，利用它可以建立可重用的代码，只是这种技术本身也存在着一些局限性。在本文中，我们将会一同探讨另外一种新的 ASP.NET 技术：用户控件。
什么是用户控件（User Controls）？
为了能更好的理解用户控件的重要性，我们先来看看一段小小的“历史”。在以前的 ASP 当中，可重用的技术实现选择是相当受限制的。许多的开发者一般都是借助将公共的常用的子过程放到那些包含文件当中的做法来实现一定的所谓代码重用的。比如，如我们想要在许多的 ASP 页面当中现实一个下拉列表框，我会在一个包含文件当中建立一个函数，样子如下所示：
Function GetListBox(asSelectedItem)
'为 HTML 的选择控件建立字符串
'返回这个字符串
End Function
当然，这样的做法的确在一定程度上做到了重用，但是为了能做到更加通用性，你不得不要增加更多的参数。为了使得类似上面的你需要整理的代码得以正常工作是困难的，因为要达到提供它的通用性（可重用性），你大概不得不去修改这些已经存在的代码，以便使得他们也能在新的环境下正常工作。
IIS5 中的 VBScript5.0 增加了建立类的功能。这就使得我们可以通过一个较多面向对象的方式来实现可重用的代码。
Class ComboBox
Property Let ControlName(vData)
.
End Property
<More properties and methods here>
End Class
这样做会稍微好一些，但是开发者仍旧需要被迫去编写那些函数，以便返回 HTML 代码。而且，他也没有能力操纵那些类的实例对象的事件。为了能做到操作事件，开发者不得不建立一些 COM 组件，而后者则增加了应用程序的额外的复杂度。
有了 ASP.NET,我们拥有了一个新的简单的工具来编写可重用的代码—用户控件。用户控件（也叫 pagelets）提供了这样一种机制，他使得我们可以建立能够非常容易的被 ASP.NET 页面使用或者重新利用的代码部件。一个用户控件也是一个简单的 ASP.NET 页面，不过它可以被另外一个 ASP.NET 页面包含进去。在你的 ASP.NET 应用程序当中使用用户控件的一个主要的优点是用户控件的支持一个完全面向对象的模式，使得你有能力去捕获事件。而且，用户控件支持你使用一种语言编写 ASP.NET 页面其中的一部分代码，而使用另外的一种语言编写 ASP.NET 页面另外一部分代码，因为每一个用户控件可以使用和主页面不同的语言来编写。
建立一个用户控件
在建立你自己的用户控件之前，你也许想知道在你的 web 页面中哪些可见的对象是能够重用的好的候选者。能可能的是，你将会在你的站点上的不止一个页面上需要使用融合的用户控件。一旦你开始不断的思考你的控件的结构，你就已经做好的开始的准备。在我们的例子当中，我们将要建立一个简单的搜索的控件，用来搜索 SQL Server2000 中的数据库 Northwind。我们的搜索控件可以使得开发者快速的为一个 web 页面增加搜索能力。
建立用户控件的第一步是建立一个.ascx 文件。这是用户控件需要的文件扩展名。在一个一个.ascx 文件中不能包含 head，form，或者 body 标签，因为包含此.ascx 文件的.aspx 文件已经包含了这些标签。一个.ascx 文件只能包含方法，函数，以及和用户控件相关的内同。
在建立一个.ascx 文件之后，我们想要为用户控件增加一些可视的代码。在一个用户控件当中可以包含所有的 web 控件。在我们的例子当中，搜索控件需要拥有一个标签，一个文本框以及一个按钮。我们首先加入这些 web 控件，因为我们的整个代码当中会涉及到这些对象。下面是具体的代码：
<asp:Label id=lblSearch runat="server" text="Caption"></asp:Label>
<asp:TextBox id=txtSearch runat="server"></asp:TextBox>
<asp:Button id=cmdSearch runat="server" Text="Search" ></asp:Button>
在用户控件中有一件很酷的事情是，你可以定义你自己的属性。在我们的例子当中，我们会定义如下属性：
。LabelText—描述显示给用户的搜索条件
。ConnectiongString---用来联接到数据库的连接字符串
。ResultSetView—包含了搜索结果的数据记录集
。TableName—要搜索的数据库 table 名称
。Condition—需要搜索的 table 的列的名称
为了建立这些属性，我们使用一些联合起来的 get 和 set 方法来同属性结合起来。在做这些工作之前，我们需要首先决定一个属性是否需要允许读，写，还是两者都需要。对于只需要读的属性，我们将会使用 ReadOnly 关键字来限定属性的声明，并且仅仅包含了 get 方法。我们的 ResultSetView 属性是一个只读的属性，因此它的相关代码看起来如下：
'这是一个只读的属性
Public ReadOnly Property ResultSetView as dataView
Get
'设置返回属性地值
ResultSetView = dsData.tables("BookTitles").defaultview
End Get
End Property
对于只需要写的属性，我们将会使用 WriteOnly 关键字来限定属性的声明，并且仅仅包含了 set 方法。我们的 TableName 属性是一个只写属性，因此它的相关代码看起来如下：
'This write only attribute identifies which table will be searched
Public WriteOnly Property TableName as string
Set
'设置表的名称
strTableName = Value
End Set
End Property
对于那些既能读也能写的属性，在定义的时候就不需要加以限定了；并且同时包含了 Get 和 Set 方法。一旦这个属性被建立，.aspx 文件就可以读取或者设置这些属性，使得用户控件可以适应一个或者多个用途了。
在用户控件当中，和定义不同的属性相结合，你也需要定义任意的方法，这些方法可以被用户控件初始化。这些属性和方法定义了用户控件的功能。在我们的例子当中使用的是 search 方法。这个方法读取各个用户定义在.aspx 文件中的控件属性，并且返回一个搜索的结果记录集。所有的操纵数据库的代码都在这个方法中了：建立 SQL 语句，打开数据库联接，并且从数据库中返回一个结果。
'这个程序依照属性的值来操纵一个数据库
Public Sub Search(sender As Object , e As System.EventArgs)
Dim cnConnection As SQLConnection
Dim cmdCommand As SQLDataSetCommand
Dim strSearchString As String
Dim strSQL As String
'如果用户在搜索框中输入了条件
If txtSearch.Text <> "" Then
'过滤掉字符的前后空格
strSearchString = trim$(txtSearch.Text)
End If
'建立我们的 SQL 语句
strSQL = "SELECT * " & _
"FROM " & strTableName & _
" WHERE " & strConditionField & " LIKE '" & _
trSearchString & "%'"
'如果联接属性被设置了
If strConnection <> "" Then
'建立数据库联接
cnConnection = New SQLConnection(strConnection)
'打开数据库联接
cnConnection.open()
'为搜索建立一个新的 command 对象
cmdCommand = New SQLDataSetCommand(strSQL, cnConnection)
'建立一个新的 DataSet 对象
dsData = New DataSet()
'填充 dataset 对象
cmdCommand.FillDataSet(dsData, "BookTitles")
End If
End Sub
一旦你在你的用户控件当中添加了属性和方法，控件的开发也就大致完成了。
现在我们对一个用户控件是如何建立的有了一个认识，让我们来看看用户控件是如何工作的。下面是我们的用户控件被包含在一个 form 当中的时候看起来的样子：
我们的用户控件将会放在一个国际食物网站的一个搜索页面上。为了建立这个搜索页面，我们先建立一个空的.aspx 文件。我们先安排好所有的图片和布局，之后加入我们的用户控件。
为了能够在一个.aspx 页面当中使用这个新的用户控件，你必须首先初始化@Register 指令。当使用这个标签的时候，你必须定义标签前缀，标签名字以及指定用户控件所在的源文件。
<%@ Register TagPrefix="jav" TagName="Search" src="SearchControl.ascx" %>
TagPrefix 定义了我们使用这个用户控件的时候想要用的名称空间。TagName 定义了这个用户控件的实际名称。你可以任意的命名你的控件，这个名字将会用在页面上面标示用户控件。
当你加入一个用户控件到一个.aspx 页面的时候，相应的语法同加入一个 web 控件类似。你首先使用标签前缀和标签名称来标示用户控件：
<jav:search></jav:search>
现在我们在页面上面有了这个空间，我们可以设置标准的 runat 和 id 属性，以及设置我们以前建立控件的时候建立的各个自己定义的属性。有两种方法来修改这些用户控件的属性（就像针对一个 ASP.NET 的 web 控件那样）。一种方法是在你的 web 页面当中引用这个用户控件的时候明确的设置它的各个属性的值。在我们的例子当中，我们设置 LabelText 属性为“Product Name”，因为我们的搜索是针对产品名称来讲的。
<jav:search id="UserControl" runat="server" labeltext="Product Name"></jav:search>
另外一种方法是在一个.aspx 文件的 page_load 事件中设置这些用户控件的属性。针对我们的例子，我们在 page_load 事件当中设置 ConnectionString，TableName，ConditionField 属性。我们需要搜索的是数据库 Northwind 中 Product 表的 ProductName 字段。
Sub page_load(objSource as Object, objArgs as eventArgs)
Dim htConfig As HashTable
'设置一个对 Config.web 文件中 AppSettings 节点的应用
htConfig = Context.GetConfig("appsettings")
'设置用户控件的连接字符串
userControl.ConnectionString=(htConfig("MyConn"))
'设置我们要搜索的表的名称
userControl.TableName="Product"
'设置我们要搜索的字段名称
userControl.ConditionField="ProductName"
对于我们的 web 站点，我们将数据库的连接字符串存放在 Config.web 文件当中的叫做 appsettings 中的区域中。（如果要使用下面的代码，请将 server 修改成你实际的 server 名字）
<appsettings>
<add key="MyConn"value="server=ServerName;uid=sa;pwd=;
database=Northwind"/>
</appsettings>
下面是我们的.aspx 文件第一次看起来的样子：
现在我们需要加入代码来调用我们的用户控件中的 search 方法。我们编写这个用户控件的例子的目的是为了实现对数据库的简单搜索以及返回搜索的结果。为了做到这些，我们有两个选择，我们要么在我们的用户控件当中将我们的结果放到我们的结果控件（在这里，一般是一个 DataGrid 控件），要么我们将一个 ADO 记录集当作一个属性返回给开发者，而不必关心开发者究竟使用什么方法来显示他。我们的选择是通过一个属性返回一个记录集结果，因为这样做可以使得开发者可以自由的选择使用什么控件以及采用什么样的数据显示方式。
然而这个选择同时也带给了我们一些问题。我们可以容易的将结果当作一个属性返回并且使用 DataSource 属性绑定到一个 DataGrid 控件或者其他的控件。但是，我们如何才能知道用户是否是点击了一个搜索按钮呢？为了解决这个问题，我们需要在 page_load 事件中使用 Page.IsPostBack 这个属性。如果一个页面是由于 post 而重载的话，属性 Page.Ispostback 的值等于 true。通过检测 page.ispostback 是否等于 true，我们可以决定是否调用我们的用户控件中的 Search 方法，然后将结果绑定到我们的 datagrid。
'检测 Page.IsPostBack 属性的值
If page.IsPostback = true then
'执行搜索的动作
userControl.Search(objSource, objArgs)
'将结果绑定到 DataGrid 控件上面
grdGrid.datasource = userControl.ResultSetView
'正式绑定数据
grdGrid.databind()
End If
End Sub
在某个人输入了一个搜索条件之后并提交我们的.aspx 文件之后的显示页面：
现在我们可以同样的建立另外一个页面以便使用在我们的站点。我们将要建立的这第二个页面同样执行一个搜索动作，但是这次是要在 Northwind 数据库的表 Company 上的 CompanyName 字段上面进行搜索匹配。为了建立这个页面，我们建立一个新的空白.aspx 文件。既然这个页面和我们的产品页面类似，我们使用相同的布局。我们将要再一次使用搜索用户控件。
我们在这个页面会将属性设置不同的值。联接字符串还是保留使用前面同样的值。我们需要设置属性 LabelText 的值等于 Company Name，设置属性 Table 的值等于 Company,设置属性 ConditionField 的值等于 CompanyName。通过对布局和用户控件的略微改动，我们就算大致完成了这个页面。对这个页面来讲，我们只需要很少的代码，这要好好谢谢用户控件带来的代码重用了。下面就是我们新的页面：
正如你所看到的，用户控件可以提供一个简便的方法来实现代码的可重用性，而省去了很多不必要的麻烦。将相关的控件和代码从一个 ASPX 文件移到一个 ASCX 文件当中是一个恰当的做法，并且只需要较小的修改就可以使得代码正常的工作了。
局限性?
你也许会问自己：我使用用户控件不能够做到什么？使用这个技术只存在很少的一些限制。
其中的一个限制是：用户控件不支持模版。因此，你不能建立一个用户控件来达到 ASP.NET 中提供的 Data Repeater 控件的功能。
另外的一个限制是：由于用户控件必须包含一些静态的 UI（用户界面）属性，所以你不能够按照你的想法严格的调整他们。比如，你可能想要建立一个包含多个控件的用户控件，并且允许开发者能够指定这些控件在页面上的显示顺序。上面的想法对于一个用户控件来说是困难的，因为 UI 块是被静态的设置的。
结论
用户控件提供了一个极好的方法使得你能够非常容易的在你的 ASP.NET 页面中实现代码的重用。将一些在 ASP.NET 中的代码拖放到 ASCX 文件中使得他们能够被重新利用是个正确的选择和做法。
在我们的文章中，我们一同探讨了通过定制的控件实现代码在 ASP.NET 中的重用。可定制的 ASP.NET 控件可以对可重用代码提供附加的弹性，正如上面我们已经学到那样。