作者 :斯科特·米切尔
在本教程中,我们继续了解 SqlDataSource 控件,并了解如何定义参数化查询。 参数可以声明方式和编程方式指定,并且可以从查询字符串、会话状态、其他控件等多个位置拉取参数。
介绍
在前面的教程中,我们了解了如何使用 SqlDataSource 控件直接从数据库检索数据。 使用“配置数据源”向导,可以选择数据库,然后选择:选择要从表或视图中返回的列;输入自定义 SQL 语句;或使用存储过程。 无论是从表或视图中选择列还是输入自定义 SQL 语句,都会为 SqlDataSource 控件的属性SelectCommand
分配生成的即席 SQL SELECT
语句,并且是在调用 SqlDataSource 方法Select()
时执行的此SELECT
语句(以编程方式或通过数据 Web 控件自动执行)。
在上一教程中的演示中使用的 SQL SELECT
语句缺少 WHERE
子句。 子句WHERE
可以用于在SELECT
语句中限制返回的结果。 例如,若要显示成本超过 50.00 美元的产品名称,可以使用以下查询:
SELECT ProductName
FROM Products
WHERE UnitPrice > 50.00
通常,子句中使用的 WHERE
值由某些外部源(例如查询字符串值、会话变量或页面上 Web 控件的用户输入)确定。 理想情况下,此类输入是通过使用 参数指定的。 使用 Microsoft SQL Server 时,参数使用 @parameterName
,如下所示:
SELECT ProductName
FROM Products
WHERE UnitPrice > @Price
SqlDataSource 支持参数化查询,包括SELECT
语句和INSERT
UPDATE
语句和DELETE
语句。 此外,参数值可以从查询字符串、会话状态、页面上的控件等各种源自动拉取,也可以以编程方式分配。 本教程介绍如何定义参数化查询,以及如何以声明方式和编程方式指定参数值。
注释
在前面的教程中,我们比较了在前 46 个教程中一直作为首选工具的 ObjectDataSource 和 SqlDataSource,并指出了它们在概念上的相似之处。 这些相似性还扩展到参数。 ObjectDataSource 的参数被映射到业务逻辑层中方法的输入参数。 使用 SqlDataSource,参数直接在 SQL 查询中定义。 这两个控件都有其Select()
参数集合、Insert()
Update()
方法和Delete()
方法,并且两者都可以从预定义源(querystring 值、会话变量等)填充这些参数值,或者以编程方式分配。
创建参数化查询
SqlDataSource 控件的“配置数据源”向导提供了三种用于定义执行以检索数据库记录的命令的途径:
- 通过从现有表或视图中选择列,
- 输入自定义 SQL 语句,或
- 通过选择存储过程
从现有表或视图中选取列时,必须通过“添加WHERE
子句”对话框指定WHERE
子句的参数。 在创建自定义 SQL 语句时,您可以直接将参数输入到 WHERE
子句中(使用 @parameterName
表示每个参数)。 存储过程由一个或多个 SQL 语句组成,这些语句可以参数化。 但是,SQL 语句中使用的参数必须作为输入参数传入存储过程。
由于创建参数化查询取决于 SqlDataSource 的 SelectCommand
指定方式,因此让我们看看这三种方法。 若要开始,请打开 ParameterizedQueries.aspx
文件夹中的页面,将工具箱中的 SqlDataSource
SqlDataSource 控件拖到设计器上,并将其设置为 ID
Products25BucksAndUnderDataSource
。 接下来,单击控件智能标记中的“配置数据源”链接。 选择要使用的数据库(NORTHWINDConnectionString
)并单击“下一步”。
步骤 1:从表或视图中选取列时添加 WHERE 子句
使用 SqlDataSource 控件选择要从数据库返回的数据时,“配置数据源”向导允许我们仅选取要从现有表或视图返回的列(请参阅图 1)。 这样做会自动生成一个 SQL SELECT
语句,这是在调用 SqlDataSource 方法 Select()
时发送到数据库的语句。 正如我们在上一教程中所做的那样,从下拉列表中选择“产品”表,然后检查ProductID
和ProductName
UnitPrice
列。
图 1:选择要从表或视图返回的列(单击以查看全尺寸图像)
要在SELECT
语句中包含WHERE
子句,请单击WHERE
按钮以显示“添加WHERE
子句”对话框(请参阅图 2)。 若要添加参数以限制查询返回 SELECT
的结果,请先选择要筛选数据的列。 接下来,选择要用于筛选的运算符(=、<、<=、>等)。 最后,选择参数值的来源,例如从查询字符串或会话状态。 配置参数后,单击“添加”按钮将其包含在查询中 SELECT
。
对于此示例,我们仅返回值小于或等于 $25.00 的那些结果 UnitPrice
。 因此,从“列”下拉列表中选择UnitPrice
并从“运算符”下拉列表中选择<= 。 使用硬编码参数值(如 $25.00)或参数值是否以编程方式指定时,请从“源”下拉列表中选择“无”。 接下来,在“值”文本框中输入硬编码参数值 25.00,然后单击“添加”按钮完成该过程。
图 2:限制“添加 WHERE
子句”对话框返回的结果(单击可查看全尺寸图像)
添加参数后,单击“确定”返回到“配置数据源”向导。 现在,向导底部的 SELECT
语句应包含一个包含参数名为 @UnitPrice
的 WHERE
条款。
SELECT [ProductID], [ProductName], [UnitPrice]
FROM [Products]
WHERE ([UnitPrice] <= @UnitPrice)
注释
如果在“添加WHERE
子句”对话框中指定子句中的WHERE
多个条件,向导会将它们与AND
运算符联接。 如果需要在WHERE
子句中包含OR
(例如WHERE UnitPrice <= @UnitPrice OR Discontinued = 1
),则必须通过自定义 SQL 语句屏幕构建SELECT
语句。
完成 SqlDataSource 配置(单击“下一步,完成”),然后检查 SqlDataSource 的声明性标记。 标记现在包括一个 <SelectParameters>
集合,该集合拼写出参数的 SelectCommand
源。
<asp:SqlDataSource ID="Products25BucksAndUnderDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
SelectCommand=
"SELECT [ProductID], [ProductName], [UnitPrice]
FROM [Products] WHERE ([UnitPrice] <= @UnitPrice)">
<SelectParameters>
<asp:Parameter DefaultValue="25.00" Name="UnitPrice" Type="Decimal" />
</SelectParameters>
</asp:SqlDataSource>
在调用 SqlDataSource 的 Select()
方法时,参数 @UnitPrice
的值将设置为参数 UnitPrice
的值(25.00),然后在发送到数据库之前将其应用于 SelectCommand
。 净结果是仅从 Products
表中返回小于或等于 $25.00 的产品。 若要确认这一点,请将 GridView 添加到页面,将其绑定到此数据源,然后通过浏览器查看页面。 如图 3 确认,应只看到列出的产品小于或等于 25.00 美元。
图 3:仅显示小于或等于 $25.00 的产品(单击以查看全尺寸图像)
步骤 2:将参数添加到自定义 SQL 语句
添加自定义 SQL 语句时,可以显式输入 WHERE
子句或在查询生成器的 Filter 单元格中指定值。 为了演示这一点,让我们在 GridView 中仅显示那些价格低于特定阈值的产品。 首先,在 ParameterizedQueries.aspx
页面中添加一个 TextBox,以便收集用户输入的阈值。 将 TextBox 属性 ID
设置为 MaxPrice
. 添加 Button Web 控件并将其属性设置为 Text
“显示匹配产品”。
接下来,将 GridView 拖到绘图页上,并从其智能标记中选择创建名为 ProductsFilteredByPriceDataSource
的新 SqlDataSource。 在“配置数据源”向导中,转到“指定自定义 SQL 语句或存储过程”屏幕(请参阅图 4),然后输入以下查询:
SELECT ProductName, UnitPrice
FROM Products
WHERE UnitPrice <= @MaximumPrice
输入查询后(手动或通过查询生成器),单击“下一步”。
图 4:仅返回小于或等于参数值的产品(单击以查看全尺寸图像)
由于查询包含参数,向导中的下一个屏幕会提示我们选择参数值来源。 从“参数源”下拉列表中选择“控件”,并从 MaxPrice
ControlID 下拉列表中选择“TextBox 控件值 ID
”。 还可以输入可选的默认值,以在用户未在 TextBox 中 MaxPrice
输入任何文本的情况下使用。 目前,不要输入默认值。
图 5: MaxPrice
TextBox s Text
属性用作参数源(单击以查看全尺寸图像)
单击“下一步”,然后单击“完成”,完成“配置数据源向导。 GridView、TextBox、Button 和 SqlDataSource 的声明性标记如下:
Maximum price:
$<asp:TextBox ID="MaxPrice" runat="server" Columns="5" />
<asp:Button ID="DisplayProductsLessThanButton" runat="server"
Text="Display Matching Products" />
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False"
DataSourceID="ProductsFilteredByPriceDataSource" EnableViewState="False">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Price"
HtmlEncode="False" DataFormatString="{0:c}"
SortExpression="UnitPrice" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="ProductsFilteredByPriceDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
SelectCommand=
"SELECT ProductName, UnitPrice
FROM Products WHERE UnitPrice <= @MaximumPrice">
<SelectParameters>
<asp:ControlParameter ControlID="MaxPrice" Name="MaximumPrice"
PropertyName="Text" />
</SelectParameters>
</asp:SqlDataSource>
请注意,SqlDataSource s <SelectParameters>
节中的参数是一个 ControlParameter
,其中包括其他属性,如 ControlID
和 PropertyName
。 调用 SqlDataSource s Select()
方法时,从ControlParameter
指定的 Web 控件属性中获取值,并将其分配给相应的参数。SelectCommand
在此示例中, MaxPrice
s Text 属性用作 @MaxPrice
参数值。
花点时间通过浏览器查看此页面。 首次访问页面或每当 MaxPrice
TextBox 缺少值时,GridView 中不会显示任何记录。
图 6:当 TextBox 为空时 MaxPrice
,不会显示任何记录(单击以查看全尺寸图像)
没有显示产品的原因是,默认情况下,参数值的空字符串转换为数据库 NULL
值。 由于比较[UnitPrice] <= NULL
时总是评估为 False,因此不返回任何结果。
在文本框中输入值,如 5.00,然后单击“显示匹配产品”按钮。 在回发时,SqlDataSource 通知 GridView 其参数源之一已更改。 因此,GridView 重新绑定到 SqlDataSource,显示这些产品小于或等于 $5.00。
图 7:显示小于或等于 $5.00 的产品(单击以查看全尺寸图像)
最初显示所有产品
我们可能需要显示所有产品,而不是在首次加载页面时不显示 任何 产品。 每当 MaxPrice
TextBox 为空时列出所有产品的方法之一是将参数的默认值设置为一些疯狂的高值,例如 1000000,因为 Northwind Traders 不太可能拥有其单价超过 1,000,000 美元的库存。 但是,此方法是短视的,在其他情况下可能不起作用。
在前面的教程 - 声明性参数 和 主从筛选与下拉列表 中,我们遇到了类似的问题。 我们的解决方案是在业务逻辑层中放置此逻辑。 具体而言,BLL 检查了传入值,如果是 NULL
或某些保留值,则调用将路由到返回所有记录的 DAL 方法。 如果传入值是普通筛选值,则对执行 SQL 语句的 DAL 方法进行调用,该语句使用具有提供的值的参数化 WHERE
子句。
遗憾的是,在使用 SqlDataSource 时,我们绕过了体系结构。 相反,我们需要自定义 SQL 语句,以便在参数为@MaximumPrice
或特定保留值时智能地获取所有记录。 在本练习中,让我们让 @MaximumPrice
参数等于 -1.0
时返回 所有 记录(-1.0
作为保留值,因为任何产品都不能有负 UnitPrice
值)。 为此,可以使用以下 SQL 语句:
SELECT ProductName, UnitPrice
FROM Products
WHERE UnitPrice <= @MaximumPrice OR @MaximumPrice = -1.0
如果@MaximumPrice
参数等于-1.0
,则此WHERE
子句返回所有记录。 如果参数值不是-1.0
,则只返回那些小于或等于@MaximumPrice
参数值的产品UnitPrice
。 通过将@MaximumPrice
参数的默认值设置为-1.0
,在首次页面加载(或每当MaxPrice
TextBox为空时),@MaximumPrice
将拥有-1.0
的值,并且所有产品都将显示。
图 8:当文本框为空时,MaxPrice
将显示所有产品(单击以查看全尺寸图像)
使用此方法需要注意几个注意事项。 首先,意识到参数的数据类型是由它在 SQL 查询中的使用情况推断的。 如果您将WHERE
子句从@MaximumPrice = -1.0
更改为@MaximumPrice = -1
,运行时会将参数视为整数。 如果随后尝试将 MaxPrice
TextBox 分配给小数值(如 5.00),则会发生错误,因为它无法将 5.00 转换为整数。 若要解决此问题,请确保 @MaximumPrice = -1.0
在 WHERE
子句中使用,或者最好地将 ControlParameter
对象 Type
属性设置为 Decimal。
其次,通过将OR @MaximumPrice = -1.0
添加到WHERE
子句,查询引擎不能使用UnitPrice
上的索引(假设存在索引),因此会导致表扫描。 如果表中有足够多的记录 Products
,这可能会影响性能。 更好的方法是将此逻辑移到存储过程中,其中IF
语句执行SELECT
查询:当需要返回所有记录时,查询不带WHERE
子句,从Products
表中获取数据;或查询带有WHERE
子句,仅包含UnitPrice
条件,以便可以使用索引。
步骤 3:创建和使用参数化存储过程
存储过程可以包含一组输入参数,这些参数随后可在存储过程中定义的 SQL 语句中使用。 将 SqlDataSource 配置为使用接受输入参数的存储过程时,可以使用与即席 SQL 语句相同的技术指定这些参数值。
为了说明如何在 SqlDataSource 中使用存储过程,让我们在 Northwind 数据库中创建一个名为GetProductsByCategory
的新存储过程,该过程接受一个名为@CategoryID
的参数,并返回所有产品的列,其中其CategoryID
列与@CategoryID
匹配。 若要创建存储过程,请转到服务器资源管理器并向下钻取到 NORTHWND.MDF
数据库。 (如果未看到服务器资源管理器,请转到“视图”菜单并选择“服务器资源管理器”选项来打开它。
NORTHWND.MDF
在数据库中,右键单击“存储过程”文件夹,选择“添加新存储过程”,然后输入以下语法:
CREATE PROCEDURE dbo.GetProductsByCategory
(
@CategoryID int
)
AS
SELECT *
FROM Products
WHERE CategoryID = @CategoryID
单击“保存”图标(或 Ctrl+S)保存存储过程。 可以通过右键单击存储过程文件夹并选择“执行”来测试存储过程。 这将提示你输入存储过程的参数(@CategoryID
在此实例中),之后结果将显示在“输出”窗口中。
图 9:GetProductsByCategory
使用 @CategoryID
为 1 执行时的存储过程(单击以查看全尺寸图像)
让我们使用此存储过程在 GridView 的“饮料”类别中显示所有产品。 向页面添加新的 GridView 并将其绑定到名为 BeverageProductsDataSource
的新 SqlDataSource。 继续转到“指定自定义 SQL 语句或存储过程”屏幕,选择存储过程单选按钮,并从下拉列表中选择 GetProductsByCategory
存储过程。
图 10: GetProductsByCategory
从 Drop-Down 列表中选择存储过程(单击以查看全尺寸图像)
由于存储过程接受输入参数(@CategoryID
),请单击“下一步”提示我们指定此参数值的源。 饮料 CategoryID
为 1,因此请将“参数源”下拉列表保留为“无”,然后在“默认值”文本框中输入 1。
图 11:使用 Hard-Coded 值 1 返回饮料类别中的产品(单击以查看全尺寸图像)
如以下声明性标记所示,使用存储过程时,SqlDataSource s SelectCommand
属性设置为存储过程的名称,并将 SelectCommandType
该属性 设置为 StoredProcedure
该属性,指示 SelectCommand
存储过程的名称而不是即席 SQL 语句。
<asp:SqlDataSource ID="BeverageProductsDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
SelectCommand="GetProductsByCategory" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:Parameter DefaultValue="1" Name="CategoryID" Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
在浏览器中测试页面。 尽管由于GetProductsByCategory
存储过程返回Products
表中的所有列,因此显示了所有产品字段,但仅展示属于“饮料”类别的产品。 当然,我们可以通过 GridView 的“编辑列”对话框限制或自定义 GridView 中显示的字段。
图 12:显示所有饮料(单击可查看全尺寸图像)
步骤 4:以编程方式调用 SqlDataSource 的 Select() 语句
在上一教程和本教程中已看到的示例迄今已将 SqlDataSource 控件直接绑定到 GridView。 但是,可以在代码中以编程方式访问和枚举 SqlDataSource 控件的数据。 当你需要查询数据来检查数据,但不需要显示数据时,这尤其有用。 无需编写所有样板 ADO.NET 代码连接到数据库、指定命令和检索结果,即可让 SqlDataSource 处理此单调代码。
为了说明以编程方式使用 SqlDataSource 数据,假设你的老板已请求你创建一个网页,用于显示随机选择类别的名称及其关联产品的名称。 也就是说,当用户访问此页面时,我们希望从 Categories
表中随机选择一个类别,显示类别名称,然后列出属于该类别的产品。
为此,我们需要两个 SqlDataSource 控件,一个控件从 Categories
表中获取一个随机类别,另一个用于获取类别的产品。 我们将在此步骤中构建检索随机类别记录的 SqlDataSource,步骤 5 将研究如何创建检索类别产品的 SqlDataSource。
首先在 ParameterizedQueries.aspx
上添加一个 SqlDataSource,并将其 ID
设置为 RandomCategoryDataSource
。 对其进行配置,使其使用以下 SQL 查询:
SELECT TOP 1 CategoryID, CategoryName
FROM Categories
ORDER BY NEWID()
ORDER BY NEWID()
返回按随机顺序排序的记录(请参阅 “使用 NEWID()
随机排序记录”)。
SELECT TOP 1
返回结果集中的第一条记录。 整体来看,此查询从单个随机选择的类别返回 CategoryID
和 CategoryName
列值。
若要显示类别 CategoryName
值,请将标签 Web 控件添加到页面,将其 ID
属性设置为 CategoryNameLabel
,并清除其 Text
属性。 若要以编程方式从 SqlDataSource 控件检索数据,需要调用其 Select()
方法。
方法Select()
期望一个类型为DataSourceSelectArguments
的单一输入参数,该参数指定在返回数据之前应如何处理信息。 这可以包括有关对数据进行排序和筛选的说明,并在从 SqlDataSource 控件对数据进行排序或分页时由数据 Web 控件使用。 不过,对于我们的示例,我们不需要在返回之前修改数据,因此将传入 DataSourceSelectArguments.Empty
对象。
该方法 Select()
返回实现 IEnumerable
的对象。 返回的精确类型取决于 SqlDataSource 控件的属性DataSourceMode
的值。 如上一教程中所述,此属性可以设置为任 DataSet
一值或 DataReader
值。 如果设置为 DataSet
,方法 Select()
将返回 DataView 对象;如果设置为 DataReader
,则返回实现 IDataReader
的对象。
RandomCategoryDataSource
由于 SqlDataSource 的属性DataSourceMode
设置为 DataSet
(默认值),因此我们将使用 DataView 对象。
以下代码演示如何从 RandomCategoryDataSource
SqlDataSource 检索记录作为 DataView,以及如何从第一个 DataView 行读取 CategoryName
列值:
protected void Page_Load(object sender, EventArgs e)
{
// Get the data from the SqlDataSource as a DataView
DataView randomCategoryView =
(DataView)RandomCategoryDataSource.Select(DataSourceSelectArguments.Empty);
if (randomCategoryView.Count > 0)
{
// Assign the CategoryName value to the Label
CategoryNameLabel.Text =
string.Format("Here are Products in the {0} Category...",
randomCategoryView[0]["CategoryName"].ToString());
}
}
randomCategoryView[0]
返回 DataView 中的第一个 DataRowView
。
randomCategoryView[0]["CategoryName"]
返回此第一行中列的值 CategoryName
。 请注意,DataView 的类型不严格。 若要引用特定的列值,我们需要以字符串形式传入列的名称(在本例中为 CategoryName)。 图 13 显示了在查看页面时CategoryNameLabel
中显示的消息。 当然,每次访问页面时,SqlDataSource 都会随机选择 RandomCategoryDataSource
显示的实际类别名称(包括回发)。
图 13:显示随机选择的类别名称(单击以查看全尺寸图像)
注释
如果 SqlDataSource 控件的属性 DataSourceMode
已设置为 DataReader
,则必须将方法的 Select()
返回值强制转换为 IDataReader
。 若要从第一行读取 CategoryName
列值,我们将使用如下代码:
if (randomCategoryReader.Read())
{
string categoryName = randomCategoryReader["CategoryName"].ToString();
...
}
通过 SqlDataSource 随机选择类别,我们准备添加列出类别产品的 GridView。
注释
我们可以将 FormView 或 DetailsView 添加到页面,而不是使用标签 Web 控件来显示类别名称,而是将其绑定到 SqlDataSource。 但是,使用 Label 允许我们探索如何以编程方式调用 SqlDataSource s Select()
语句,并在代码中处理其生成的数据。
步骤 5:以编程方式分配参数值
到目前为止,在本教程中看到的所有示例都使用了硬编码参数值或从其中一个预定义参数源(查询字符串值、页面上的 Web 控件等)获取的参数值。 但是,还可以以编程方式设置 SqlDataSource 控件的参数。 若要完成当前示例,我们需要一个 SqlDataSource,该 SqlDataSource 返回属于指定类别的所有产品。 此 SqlDataSource 将具有一个CategoryID
参数,该参数的值需要基于CategoryID
事件处理程序中 SqlDataSource 返回的RandomCategoryDataSource
Page_Load
列值进行设置。
首先将 GridView 添加到页面,并将其绑定到名为 ProductsByCategoryDataSource
的新 SqlDataSource。 就像我们在步骤 3 中所做的那样,配置 SqlDataSource,以便调用 GetProductsByCategory
存储过程。 将参数源下拉列表设置为“无”,但不输入默认值,因为我们将以编程方式设置此默认值。
图 14:请勿指定参数源或默认值(单击以查看全尺寸图像)
完成 SqlDataSource 向导后,生成的声明性标记应如下所示:
<asp:SqlDataSource ID="ProductsByCategoryDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
SelectCommand="GetProductsByCategory" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
我们可以通过程序在Page_Load
事件处理程序中分配DefaultValue
的CategoryID
参数。
// Assign the ProductsByCategoryDataSource's
// CategoryID parameter's DefaultValue property
ProductsByCategoryDataSource.SelectParameters["CategoryID"].DefaultValue =
randomCategoryView[0]["CategoryID"].ToString();
添加后,页面包含一个 GridView,它显示与随机选择的类别关联的产品。
图 15:不指定参数源或默认值(单击可查看全尺寸图像)
概要
SqlDataSource 使页面开发人员能够定义参数化查询,这些查询的参数值可以是硬编码的、从预定义的参数源中提取的,也可以以编程方式分配。 本教程介绍了如何为即席 SQL 查询和存储过程从“配置数据源”向导创建参数化查询。 我们还介绍了如何使用硬编码的参数源、Web 控件作为参数源,以及以编程方式指定参数值。
与 ObjectDataSource 一样,SqlDataSource 还提供修改其基础数据的功能。 在下一教程中,我们将了解如何使用 SqlDataSource 定义INSERT
和UPDATE
DELETE
语句。 添加这些语句后,我们可以利用 GridView、DetailsView 和 FormView 控件固有的内置插入、编辑和删除功能。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别致谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Scott Clyde、Randell Schmidt 和 Ken Pespisa。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com