检查与插入、更新和删除操作有关的事件 (C#)Examining the Events Associated with Inserting, Updating, and Deleting (C#)

作者: Scott Mitchellby Scott Mitchell

下载示例应用下载 PDFDownload Sample App or Download PDF

在本教程中,我们将使用 ASP.NET 数据 Web 控件的插入、更新或删除操作前后的事件进行检查。In this tutorial we'll examine using the events that occur before, during, and after an insert, update, or delete operation of an ASP.NET data Web control. 我们还将了解如何自定义编辑界面,以便仅更新产品字段的子集。We'll also see how to customize the editing interface to only update a subset of the product fields.

简介Introduction

使用内置的插入、编辑或删除 GridView、DetailsView 或 FormView 控件的功能时,最终用户完成添加新记录或更新或删除现有记录的过程时,会发生各种步骤。When using the built-in inserting, editing, or deleting features of the GridView, DetailsView, or FormView controls, a variety of steps transpire when the end user completes the process of adding a new record or updating or deleting an existing record. 前面的教程中所述,在 GridView 中编辑某一行时,"编辑" 按钮将被 "更新" 和 "取消" 按钮替换,并且 BoundFields 转换为文本框。As we discussed in the previous tutorial, when a row is edited in the GridView the Edit button is replaced by Update and Cancel buttons and the BoundFields turn into TextBoxes. 最终用户更新数据并单击 "更新" 后,将对回发执行以下步骤:After the end user updates the data and clicks Update, the following steps are performed on postback:

  1. GridView 使用已编辑记录的唯一标识字段(通过 DataKeyNames 属性)和用户输入的值填充其 ObjectDataSource 的 UpdateParametersThe GridView populates its ObjectDataSource's UpdateParameters with the edited record's unique identifying field(s) (via the DataKeyNames property) along with the values entered by the user
  2. GridView 调用其 ObjectDataSource Update() 方法,该方法反过来调用基础对象中的相应方法(ProductsDAL.UpdateProduct,在前面的教程中)The GridView invokes its ObjectDataSource's Update() method, which in turn invokes the appropriate method in the underlying object (ProductsDAL.UpdateProduct, in our previous tutorial)
  3. 底层数据现在包含更新的更改,将重新绑定到 GridViewThe underlying data, which now includes the updated changes, is rebound to the GridView

在此步骤序列过程中,会触发大量事件,使我们能够创建事件处理程序,以便在需要时添加自定义逻辑。During this sequence of steps, a number of events fire, enabling us to create event handlers to add custom logic where needed. 例如,在步骤1之前,激发了 GridView 的 RowUpdating 事件。For example, prior to Step 1, the GridView's RowUpdating event fires. 如果出现某些验证错误,我们可以在此时取消更新请求。We can, at this point, cancel the update request if there is some validation error. 调用 Update() 方法时,将激发 ObjectDataSource 的 Updating 事件,从而提供添加或自定义任意 UpdateParameters的值的机会。When the Update() method is invoked, the ObjectDataSource's Updating event fires, providing an opportunity to add or customize the values of any of the UpdateParameters. 在 ObjectDataSource 的基础对象的方法完成执行后,将引发 ObjectDataSource 的 Updated 事件。After the ObjectDataSource's underlying object's method has completed executing, the ObjectDataSource's Updated event is raised. Updated 事件的事件处理程序可以检查有关更新操作的详细信息,例如,受影响的行数以及是否发生了异常。An event handler for the Updated event can inspect the details about the update operation, such as how many rows were affected and whether or not an exception occurred. 最后,在步骤2之后,将激发 GridView 的 RowUpdated 事件;此事件的事件处理程序可以检查有关刚执行的更新操作的其他信息。Finally, after Step 2, the GridView's RowUpdated event fires; an event handler for this event can examine additional information about the update operation just performed.

图1描述了更新 GridView 时的这一系列事件和步骤。Figure 1 depicts this series of events and steps when updating a GridView. 图1中的事件模式对于使用 GridView 进行更新并不是唯一的。The event pattern in Figure 1 is not unique to updating with a GridView. 从 GridView、DetailsView 或 FormView precipitates 插入、更新或删除数据时,对于数据 Web 控件和 ObjectDataSource,都具有相同的前期和后期级别事件的序列。Inserting, updating, or deleting data from the GridView, DetailsView, or FormView precipitates the same sequence of pre- and post-level events for both the data Web control and the ObjectDataSource.

在 GridView 中更新数据时,一系列事件和事件后激发A Series of Pre- and Post-Events Fire When Updating Data in a GridView

图 1:更新 GridView 中的数据时,一系列事件和事件后激发(单击查看完全大小的图像Figure 1: A Series of Pre- and Post-Events Fire When Updating Data in a GridView (Click to view full-size image)

在本教程中,我们将检查如何使用这些事件来扩展 ASP.NET 数据 Web 控件的内置插入、更新和删除功能。In this tutorial we'll examine using these events to extend the built-in inserting, updating, and deleting capabilities of the ASP.NET data Web controls. 我们还将了解如何自定义编辑界面,以便仅更新产品字段的子集。We'll also see how to customize the editing interface to only update a subset of the product fields.

步骤1:更新产品的ProductNameUnitPrice字段Step 1: Updating a Product'sProductNameandUnitPriceFields

在上一教程中,编辑界面中的所有不是只读的产品字段都必须包括在内。In the editing interfaces from the previous tutorial all product fields that were not read-only had to be included. 如果我们要从 GridView 中删除字段(例如 QuantityPerUnit-更新数据时,数据 Web 控件不会将 ObjectDataSource 的 QuantityPerUnit UpdateParameters 值。If we were to remove a field from the GridView - say QuantityPerUnit - when updating the data the data Web control would not set the ObjectDataSource's QuantityPerUnit UpdateParameters value. 然后,ObjectDataSource 将 null 值传入 UpdateProduct 业务逻辑层(BLL)方法,该方法会将编辑的数据库记录的 QuantityPerUnit 列更改为 NULL 值。The ObjectDataSource would then pass in a null value into the UpdateProduct Business Logic Layer (BLL) method, which would change the edited database record's QuantityPerUnit column to a NULL value. 同样,如果从编辑界面删除了某个必填字段(如 ProductName),则更新将失败,并出现 "列 ' ProductName ' 不允许空值" 异常。Similarly, if a required field, such as ProductName, is removed from the editing interface, the update will fail with a "Column 'ProductName' does not allow nulls" exception. 此行为的原因是,ObjectDataSource 配置为调用 ProductsBLL 类的 UpdateProduct 方法,该方法需要每个产品字段的输入参数。The reason for this behavior was because the ObjectDataSource was configured to call the ProductsBLL class's UpdateProduct method, which expected an input parameter for each of the product fields. 因此,ObjectDataSource 的 UpdateParameters 集合包含该方法的每个输入参数的参数。Therefore, the ObjectDataSource's UpdateParameters collection contained a parameter for each of the method's input parameters.

如果我们想要提供一个允许最终用户仅更新字段子集的数据 Web 控件,则需要以编程方式在 ObjectDataSource 的 Updating 事件处理程序中设置缺少的 UpdateParameters 值,或者创建和调用只需要一部分字段的 BLL 方法。If we want to provide a data Web control that allows the end user to only update a subset of fields, then we need to either programmatically set the missing UpdateParameters values in the ObjectDataSource's Updating event handler or create and call a BLL method that expects only a subset of the fields. 让我们探讨后一种方法。Let's explore this latter approach.

具体而言,我们将创建一个页面,只显示可编辑 GridView 中的 ProductNameUnitPrice 字段。Specifically, let's create a page that displays just the ProductName and UnitPrice fields in an editable GridView. 此 GridView 的编辑界面只允许用户更新两个显示的字段 ProductNameUnitPriceThis GridView's editing interface will only allow the user to update the two displayed fields, ProductName and UnitPrice. 由于此编辑接口仅提供产品字段的子集,因此,我们需要创建一个 ObjectDataSource,该 ObjectDataSource 使用现有的 BLL UpdateProduct 方法,并在其 Updating 事件处理程序中以编程方式设置缺失的产品字段值,或者我们需要创建一个新的 BLL 方法,该方法只需要在 GridView 中定义的字段的子集。Since this editing interface only provides a subset of a product's fields, we either need to create an ObjectDataSource that uses the existing BLL's UpdateProduct method and has the missing product field values set programmatically in its Updating event handler, or we need to create a new BLL method that expects only the subset of fields defined in the GridView. 对于本教程,我们将使用后一个选项,并创建 UpdateProduct 方法的重载,该重载只使用三个输入参数: productNameunitPriceproductIDFor this tutorial, let's use the latter option and create an overload of the UpdateProduct method, one that takes in just three input parameters: productName, unitPrice, and productID:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
    Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;

    Northwind.ProductsRow product = products[0];

    product.ProductName = productName;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;

    // Update the product record
    int rowsAffected = Adapter.Update(product);

    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

与原始 UpdateProduct 方法一样,此重载通过检查数据库中是否存在具有指定 ProductID的产品开始。Like the original UpdateProduct method, this overload starts by checking to see if there is a product in the database with the specified ProductID. 否则,它将返回 false,指示更新产品信息的请求失败。If not, it returns false, indicating that the request to update the product information failed. 否则,它会相应地更新现有产品记录的 ProductNameUnitPrice 字段,并通过调用 TableAdapter 的 Update() 方法,并传入 ProductsRow 实例来提交更新。Otherwise it updates the existing product record's ProductName and UnitPrice fields accordingly and commits the update by calling the TableAdapter's Update() method, passing in the ProductsRow instance.

通过此类添加到 ProductsBLL 类,可以创建简化的 GridView 界面。With this addition to our ProductsBLL class, we're ready to create the simplified GridView interface. 打开 EditInsertDelete 文件夹中的 DataModificationEvents.aspx,并向页面添加 GridView。Open the DataModificationEvents.aspx in the EditInsertDelete folder and add a GridView to the page. 创建新的 ObjectDataSource,并将其配置为使用 ProductsBLL 类,并将其 Select() 方法映射到 GetProducts,并将其 Update() 方法映射到仅采用 UpdateProductproductNameunitPrice输入参数的 productID 重载。Create a new ObjectDataSource and configure it to use the ProductsBLL class with its Select() method mapping to GetProducts and its Update() method mapping to the UpdateProduct overload that takes in only the productName, unitPrice, and productID input parameters. 图2显示了在将 ObjectDataSource 的 Update() 方法映射到 ProductsBLL 类的新 UpdateProduct 方法重载时创建数据源向导。Figure 2 shows the Create Data Source wizard when mapping the ObjectDataSource's Update() method to the ProductsBLL class's new UpdateProduct method overload.

将 ObjectDataSource 的 Update ()方法映射到新的 UpdateProduct 重载Map the ObjectDataSource's Update() Method to the New UpdateProduct Overload

图 2:将 ObjectDataSource 的 Update() 方法映射到新的 UpdateProduct 重载(单击以查看完全大小的映像Figure 2: Map the ObjectDataSource's Update() Method to the New UpdateProduct Overload (Click to view full-size image)

由于我们的示例最初只需要能够编辑数据,而不是插入或删除记录,因此请花一些时间来明确指出,不应通过转到 "插入" 和 "删除" 选项卡,然后从下拉列表中选择 "(无)" 来将 ObjectDataSource 的 Insert()Delete() 方法映射到任何 ProductsBLL 类的方法。Since our example will initially just need the ability to edit data, but not to insert or delete records, take a moment to explicitly indicate that the ObjectDataSource's Insert() and Delete() methods shouldn't be mapped to any of the ProductsBLL class's methods by going to the INSERT and DELETE tabs and choosing (None) from the drop-down list.

从 "插入" 和 "删除" 选项卡的下拉列表中选择 "(无)" Choose (None) From the Drop-Down List for the INSERT and DELETE Tabs

图 3:从 "插入" 和 "删除" 选项卡的下拉列表中选择 "(无)" (单击查看完全大小的图像Figure 3: Choose (None) From the Drop-Down List for the INSERT and DELETE Tabs (Click to view full-size image)

完成此向导后,请从 GridView 的智能标记中选中 "启用编辑" 复选框。After completing this wizard check the Enable Editing checkbox from the GridView's smart tag.

完成创建数据源向导并将其绑定到 GridView 后,Visual Studio 将为这两个控件创建了声明性语法。With the completion of the Create Data Source wizard and binding that to the GridView, Visual Studio has created the declarative syntax for both controls. 请中转到 "源" 视图以检查 ObjectDataSource 的声明性标记,如下所示:Go to the Source view to inspect the ObjectDataSource's declarative markup, which is shown below:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL" UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

由于不存在用于 ObjectDataSource 的 Insert()Delete() 方法的映射,因此没有 InsertParametersDeleteParameters 部分。Since there are no mappings for the ObjectDataSource's Insert() and Delete() methods, there are no InsertParameters or DeleteParameters sections. 此外,由于 Update() 方法映射到仅接受三个输入参数的 UpdateProduct 方法重载,UpdateParameters 节只包含三个 Parameter 实例。Furthermore, since the Update() method is mapped to the UpdateProduct method overload that only accepts three input parameters, the UpdateParameters section has just three Parameter instances.

请注意,ObjectDataSource 的 OldValuesParameterFormatString 属性设置为 original_{0}Note that the ObjectDataSource's OldValuesParameterFormatString property is set to original_{0}. 使用 "配置数据源" 向导时,Visual Studio 会自动设置此属性。This property is set automatically by Visual Studio when using the Configure Data Source wizard. 但是,由于 BLL 方法不希望传入原始 ProductID 值,因此请从 ObjectDataSource 的声明性语法中删除此属性赋值。However, since our BLL methods don't expect the original ProductID value to be passed in, remove this property assignment altogether from the ObjectDataSource's declarative syntax.

Note

如果只是从设计视图的属性窗口中清除 OldValuesParameterFormatString 属性值,则该属性仍将在声明性语法中存在,但会设置为空字符串。If you simply clear out the OldValuesParameterFormatString property value from the Properties window in the Design view, the property will still exist in the declarative syntax, but will be set to an empty string. 从声明性语法中删除完全相同的属性,或者从属性窗口中将此值设置为默认值,{0}Either remove the property altogether from the declarative syntax or, from the Properties window, set the value to the default, {0}.

尽管 ObjectDataSource 只为产品的名称、价格和 ID UpdateParameters,但 Visual Studio 已为产品的每个字段在 GridView 中添加了 BoundField 或 CheckBoxField。While the ObjectDataSource only has UpdateParameters for the product's name, price, and ID, Visual Studio has added a BoundField or CheckBoxField in the GridView for each of the product's fields.

GridView 包含每个产品字段的 BoundField 或 CheckBoxFieldThe GridView Contains a BoundField or CheckBoxField for Each of the Product's Fields

图 4: GridView 包含产品每个字段的 BoundField 或 CheckBoxField (单击以查看完全大小的图像Figure 4: The GridView Contains a BoundField or CheckBoxField for Each of the Product's Fields (Click to view full-size image)

当最终用户编辑产品并单击其 "更新" 按钮时,GridView 会枚举不是只读字段的字段。When the end user edits a product and clicks its Update button, the GridView enumerates those fields that were not read-only. 然后,它将 ObjectDataSource 的 UpdateParameters 集合中相应参数的值设置为用户输入的值。It then sets the value of the corresponding parameter in the ObjectDataSource's UpdateParameters collection to the value entered by the user. 如果没有相应的参数,则 GridView 会将其添加到集合中。If there is not a corresponding parameter, the GridView adds one to the collection. 因此,如果 GridView 包含所有产品的字段的 BoundFields 和 CheckBoxFields,则 ObjectDataSource 最终将调用获取所有这些参数的 UpdateProduct 重载,尽管 ObjectDataSource 的声明性标记只指定了三个输入参数(参见图5)。Therefore, if our GridView contains BoundFields and CheckBoxFields for all of the product's fields, the ObjectDataSource will end up invoking the UpdateProduct overload that takes in all of these parameters, despite the fact that the ObjectDataSource's declarative markup specifies only three input parameters (see Figure 5). 同样,如果 GridView 中存在与 UpdateProduct 重载的输入参数不对应的非只读 product 字段的组合,则在尝试更新时将引发异常。Similarly, if there is some combination of non-read-only product fields in the GridView that doesn't correspond to the input parameters for a UpdateProduct overload, an exception will be raised when attempting to update.

GridView 会将参数添加到 ObjectDataSource 的 UpdateParameters 集合中The GridView Will Add Parameters to the ObjectDataSource's UpdateParameters Collection

图 5: GridView 将参数添加到 ObjectDataSource 的 UpdateParameters 集合(单击查看完全大小的映像Figure 5: The GridView Will Add Parameters to the ObjectDataSource's UpdateParameters Collection (Click to view full-size image)

若要确保 ObjectDataSource 调用只采用产品名称、价格和 ID 的 UpdateProduct 重载,则需要将 GridView 限制为仅具有 ProductNameUnitPrice的可编辑字段。To ensure that the ObjectDataSource invokes the UpdateProduct overload that takes in just the product's name, price, and ID, we need to restrict the GridView to having editable fields for just the ProductName and UnitPrice. 这可以通过以下方式实现:删除其他 BoundFields 和 CheckBoxFields,将其他字段的 ReadOnly 属性设置为 true或这两者的组合。This can be accomplished by removing the other BoundFields and CheckBoxFields, by setting those other fields' ReadOnly property to true, or by some combination of the two. 对于本教程,我们只需删除除 ProductNameUnitPrice BoundFields 以外的所有 GridView 字段,之后 GridView 的声明性标记将如下所示:For this tutorial let's simply remove all GridView fields except the ProductName and UnitPrice BoundFields, after which the GridView's declarative markup will look like:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
    <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:BoundField DataField="ProductName"
          HeaderText="ProductName" SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
          SortExpression="UnitPrice" />
    </Columns>
</asp:GridView>

即使 UpdateProduct 重载需要三个输入参数,在 GridView 中我们还是只有两 BoundFields。Even though the UpdateProduct overload expects three input parameters, we only have two BoundFields in our GridView. 这是因为 productID 输入参数是一个主键值,并且通过所编辑的行的 DataKeyNames 属性的值进行传递。This is because the productID input parameter is a primary key value and passed in through the value of the DataKeyNames property for the edited row.

我们的 GridView 与 UpdateProduct 重载一起使用,允许用户仅编辑产品的名称和价格,而不会丢失任何其他产品字段。Our GridView, along with the UpdateProduct overload, allows a user to edit just the name and price of a product without losing any of the other product fields.

界面仅允许编辑产品的名称和价格The Interface Allows Editing Just the Product's Name and Price

图 6:接口只允许编辑产品的名称和价格(单击查看全尺寸图像Figure 6: The Interface Allows Editing Just the Product's Name and Price (Click to view full-size image)

Note

如前面的教程中所述,必须启用 GridView 的视图状态(默认行为),这一点至关重要。As discussed in the previous tutorial, it is vitally important that the GridView s view state be enabled (the default behavior). 如果将 GridView EnableViewState 属性设置为 "false",则可能会遇到使并发用户无意中删除或编辑记录的风险。If you set the GridView s EnableViewState property to false, you run the risk of having concurrent users unintentionally deleting or editing records. 有关详细信息,请参阅ASP.NET 2.0 GridViews/DetailsView/FormViews 的并发问题,其中支持编辑和/或删除并禁用了其视图状态See WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled for more information.

改善UnitPrice格式Improving theUnitPriceFormatting

虽然图6中所示的 GridView 示例是可行的,但 "UnitPrice" 字段不是格式的,这会导致价格显示缺少任何货币符号,且具有四个小数位。While the GridView example shown in Figure 6 works, the UnitPrice field is not formatted at all, resulting in a price display that lacks any currency symbols and has four decimal places. 若要为不可编辑的行应用货币格式设置,只需将 UnitPrice BoundField 的 DataFormatString 属性设置为 {0:c},并将其 HtmlEncode 属性设置为 falseTo apply a currency formatting for the non-editable rows, simply set the UnitPrice BoundField's DataFormatString property to {0:c} and its HtmlEncode property to false.

设置 DataFormatString 和 Server.htmlencode 属性Set the UnitPrice's DataFormatString and HtmlEncode Properties Accordingly

图 7:相应地设置 UnitPriceDataFormatStringHtmlEncode 属性(单击以查看完全大小的图像Figure 7: Set the UnitPrice's DataFormatString and HtmlEncode Properties Accordingly (Click to view full-size image)

进行此更改后,不可编辑的行将价格设置为货币格式;但已编辑的行仍会显示值,但不包含货币符号和四个小数位。With this change, the non-editable rows format the price as a currency; the edited row, however, still displays the value without the currency symbol and with four decimal places.

现在 不可编辑的行的格式设置为货币值Non-Editable Rows are Now Formatted as Currency Values

图 8:不可编辑的行现在已格式化为货币值(单击以查看完全大小的图像Figure 8: Non-Editable Rows are Now Formatted as Currency Values (Click to view full-size image)

通过将 BoundField 的 ApplyFormatInEditMode 属性设置为 true (默认值为 false),可以将 DataFormatString 属性中指定的格式设置指令应用于编辑界面。The formatting instructions specified in the DataFormatString property can be applied to the editing interface by setting the BoundField's ApplyFormatInEditMode property to true (the default is false). 请花点时间将此属性设置为 trueTake a moment to set this property to true.

将 BoundField 的 ApplyFormatInEditMode 属性设置为 trueSet the UnitPrice BoundField's ApplyFormatInEditMode property to true

图 9:将 UnitPrice BoundField 的 ApplyFormatInEditMode 属性设置为 true单击查看完全大小的图像Figure 9: Set the UnitPrice BoundField's ApplyFormatInEditMode property to true (Click to view full-size image)

进行此更改后,所编辑的行中显示的 UnitPrice 的值也将设置为货币格式。With this change, the value of the UnitPrice displayed in the edited row is also formatted as a currency.

编辑的行的 "单价" 值现在已设置为货币格式The Edited Row's UnitPrice Value is Now Formatted as a Currency

图 10:已编辑的行的 UnitPrice 值现在已设置为货币格式(单击查看完全尺寸的图像Figure 10: The Edited Row's UnitPrice Value is Now Formatted as a Currency (Click to view full-size image)

但是,在文本框(如 $19.00)中使用货币符号更新产品会引发 FormatExceptionHowever, updating a product with the currency symbol in the textbox such as $19.00 throws a FormatException. 当 GridView 尝试将用户提供的值分配给 ObjectDataSource 的 UpdateParameters 集合时,它无法将 UnitPrice 字符串 "$19.00" 转换为参数所需的 decimal (参见图11)。When the GridView attempts to assign the user-supplied values to the ObjectDataSource's UpdateParameters collection it is unable to convert the UnitPrice string "$19.00" into the decimal required by the parameter (see Figure 11). 若要解决此情况,可以为 GridView 的 RowUpdating 事件创建事件处理程序,并让它将用户提供的 UnitPrice 分析为货币格式的 decimalTo remedy this we can create an event handler for the GridView's RowUpdating event and have it parse the user-supplied UnitPrice as a currency-formatted decimal.

GridView 的 RowUpdating 事件接受GridViewUpdateEventArgs类型的对象作为其第二个参数,其中包含一个 NewValues 字典作为其属性之一,其中包含可分配给 ObjectDataSource 的 UpdateParameters 集合的用户提供的值。The GridView's RowUpdating event accepts as its second parameter an object of type GridViewUpdateEventArgs, which includes a NewValues dictionary as one of its properties that holds the user-supplied values ready to be assigned to the ObjectDataSource's UpdateParameters collection. 我们可以使用以下代码行在 RowUpdating 事件处理程序中,用货币格式分析的十进制值覆盖 NewValues 集合中的现有 UnitPrice 值:We can overwrite the existing UnitPrice value in the NewValues collection with a decimal value parsed using the currency format with the following lines of code in the RowUpdating event handler:

protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
  if (e.NewValues["UnitPrice"] != null)
    e.NewValues["UnitPrice"] =
        decimal.Parse(e.NewValues["UnitPrice"].ToString(),
            System.Globalization.NumberStyles.Currency);
}

如果用户提供了一个 UnitPrice 值(如 "$19.00"),则使用 Decimal 计算所得的十进制值覆盖此值。 Parse,将值分析为货币。If the user has supplied a UnitPrice value (such as "$19.00"), this value is overwritten with the decimal value computed by Decimal.Parse, parsing the value as a currency. 这将在任何货币符号、逗号、小数点等的情况下正确分析 decimal,并使用 NumberStyles命名空间中的枚举This will correctly parse the decimal in the event of any currency symbols, commas, decimal points, and so on, and uses the NumberStyles enumeration in the System.Globalization namespace.

图11显示了用户提供的 UnitPrice中的货币符号导致的问题,以及如何利用 GridView 的 RowUpdating 事件处理程序来正确分析此类输入。Figure 11 shows both the problem caused by currency symbols in the user-supplied UnitPrice, along with how the GridView's RowUpdating event handler can be utilized to correctly parse such input.

编辑的行的 "单价" 值现在已设置为货币格式The Edited Row's UnitPrice Value is Now Formatted as a Currency

图 11:已编辑的行的 UnitPrice 值现在已设置为货币格式(单击查看完全尺寸的图像Figure 11: The Edited Row's UnitPrice Value is Now Formatted as a Currency (Click to view full-size image)

步骤2:禁止NULL UnitPricesStep 2: ProhibitingNULL UnitPrices

当数据库被配置为允许 Products 表的 UnitPrice 列中 NULL 值时,我们可能希望阻止用户通过指定 NULL UnitPrice 值来访问此特定页面。While the database is configured to allow NULL values in the Products table's UnitPrice column, we may want to prevent users visiting this particular page from specifying a NULL UnitPrice value. 也就是说,如果用户在编辑产品行时无法输入 UnitPrice 值,而不是将结果保存到数据库中,我们想要显示一条消息,告知用户在此页上,任何已编辑的产品都必须指定价格。That is, if a user fails to enter a UnitPrice value when editing a product row, rather than save the results to the database we want to display a message informing the user that, through this page, any edited products must have a price specified.

传递到 GridView RowUpdating 事件处理程序中的 GridViewUpdateEventArgs 对象包含一个 Cancel 属性,如果设置为 true,则终止更新过程。The GridViewUpdateEventArgs object passed into the GridView's RowUpdating event handler contains a Cancel property that, if set to true, terminates the updating process. 接下来,我们将扩展 RowUpdating 事件处理程序,将 e.Cancel 设置为 true 并显示一条消息,说明 NewValues 集合中的 UnitPrice 值是否 nullLet's extend the RowUpdating event handler to set e.Cancel to true and display a message explaining why if the UnitPrice value in the NewValues collection is null.

首先,将 "标签" Web 控件添加到名为 "MustProvideUnitPriceMessage" 的页。Start by adding a Label Web control to the page named MustProvideUnitPriceMessage. 如果在更新产品时用户未能指定 UnitPrice 值,则将显示此 "标签" 控件。This Label control will be displayed if the user fails to specify a UnitPrice value when updating a product. 将标签的 Text 属性设置为 "必须提供产品价格"。Set the Label's Text property to "You must provide a price for the product." 我还使用以下定义,在名为 WarningStyles.css 中创建了一个新的 CSS 类:I've also created a new CSS class in Styles.css named Warning with the following definition:

.Warning
{
    color: Red;
    font-style: italic;
    font-weight: bold;
    font-size: x-large;
}

最后,将标签的 CssClass 属性设置为 WarningFinally, set the Label's CssClass property to Warning. 此时,设计器应显示警告消息,其中红色、粗体、斜体、小字体大小在 GridView 之上,如图12所示。At this point the Designer should show the warning message in a red, bold, italic, extra large font size above the GridView, as shown in Figure 12.

在 GridView 上方添加标签A Label Has Been Added Above the GridView

图 12:已在 GridView 上方添加标签(单击以查看完全尺寸的图像Figure 12: A Label Has Been Added Above the GridView (Click to view full-size image)

默认情况下,应隐藏此标签,以便将其 Visible 属性设置为在 Page_Load 事件处理程序中 falseBy default, this Label should be hidden, so set its Visible property to false in the Page_Load event handler:

protected void Page_Load(object sender, EventArgs e)
{
    MustProvideUnitPriceMessage.Visible = false;
}

如果用户在未指定 UnitPrice的情况下尝试更新产品,我们想要取消更新并显示警告标签。If the user attempts to update a product without specifying the UnitPrice, we want to cancel the update and display the warning label. 增加 GridView 的 RowUpdating 事件处理程序,如下所示:Augment the GridView's RowUpdating event handler as follows:

protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    if (e.NewValues["UnitPrice"] != null)
    {
        e.NewValues["UnitPrice"] =
            decimal.Parse(e.NewValues["UnitPrice"].ToString(),
                System.Globalization.NumberStyles.Currency);
    }
    else
    {
        // Show the Label
        MustProvideUnitPriceMessage.Visible = true;

        // Cancel the update
        e.Cancel = true;
    }
}

如果用户在未指定价格的情况下尝试保存产品,则将取消更新并显示有用的消息。If a user attempts to save a product without specifying a price, the update is cancelled and a helpful message is displayed. 尽管数据库(和业务逻辑)允许 NULL UnitPrice,但这一特定的 ASP.NET 页面并不允许。While the database (and business logic) allows for NULL UnitPrice s, this particular ASP.NET page does not.

用户不能将单价留空A User Cannot Leave UnitPrice Blank

图 13:用户不能将 UnitPrice 留空(单击查看完全尺寸的图像Figure 13: A User Cannot Leave UnitPrice Blank (Click to view full-size image)

到目前为止,我们已了解如何使用 GridView 的 RowUpdating 事件以编程方式更改分配给 ObjectDataSource 的 UpdateParameters 集合的参数值,以及如何完全取消更新过程。So far we have seen how to use the GridView's RowUpdating event to programmatically alter the parameter values assigned to the ObjectDataSource's UpdateParameters collection as well as how to cancel the updating process altogether. 这些概念将传输到 DetailsView 和 FormView 控件,并适用于插入和删除。These concepts carry over to the DetailsView and FormView controls and also apply to inserting and deleting.

还可以通过事件处理程序在 ObjectDataSource 级别为其 InsertingUpdatingDeleting 事件完成这些任务。These tasks can also be done at the ObjectDataSource level through event handlers for its Inserting, Updating, and Deleting events. 这些事件在调用基础对象的关联方法之前激发,并为最后修改输入参数集合或完全取消操作提供了最后机会。These events fire before the associated method of the underlying object is invoked and provide a last-chance opportunity to modify the input parameters collection or cancel the operation outright. 这三个事件的事件处理程序将传递ObjectDataSourceMethodEventArgs类型的对象,该对象具有两个相关属性:The event handlers for these three events are passed an object of type ObjectDataSourceMethodEventArgs that has two properties of interest:

  • 取消,此操作如果设置为 true,则取消正在执行的操作Cancel, which, if set to true, cancels the operation being performed
  • 输入参数,它是 InsertParametersUpdateParametersDeleteParameters的集合,具体取决于事件处理程序是用于 InsertingUpdating还是 Deleting 事件InputParameters, which is the collection of InsertParameters, UpdateParameters, or DeleteParameters, depending on whether the event handler is for the Inserting, Updating, or Deleting event

为了说明如何在 ObjectDataSource 级别使用参数值,让我们在页面中包含一项 DetailsView,使用户能够添加新产品。To illustrate working with the parameter values at the ObjectDataSource level, let's include a DetailsView in our page that allows the users to add a new product. 此 DetailsView 将用于提供一个界面,用于快速将新产品添加到数据库。This DetailsView will be used to provide an interface for quickly adding a new product to the database. 若要在添加新产品时保持一致的用户界面,则允许用户仅输入 "ProductName" 和 "UnitPrice" 字段的值。To keep a consistent user interface when adding a new product let's allow the user to only enter values for the ProductName and UnitPrice fields. 默认情况下,DetailsView 的插入界面中未提供的那些值将设置为 NULL 数据库值。By default, those values that aren't supplied in the DetailsView's inserting interface will be set to a NULL database value. 但是,我们可以使用 ObjectDataSource 的 Inserting 事件注入不同的默认值,正如我们稍后将看到的那样。However, we can use the ObjectDataSource's Inserting event to inject different default values, as we'll see shortly.

步骤3:提供添加新产品的接口Step 3: Providing an Interface to Add New Products

将 DetailsView 从工具箱拖到 GridView 上方的设计器中,清除其 HeightWidth 属性,并将其绑定到页面上已存在的 ObjectDataSource。Drag a DetailsView from the Toolbox onto the Designer above the GridView, clear out its Height and Width properties, and bind it to the ObjectDataSource already present on the page. 这会为产品的每个字段添加 BoundField 或 CheckBoxField。This will add a BoundField or CheckBoxField for each of the product's fields. 由于我们希望使用此 DetailsView 来添加新产品,因此需要从智能标记中选中 "启用插入" 选项;但是,没有这样的选项,因为 ObjectDataSource 的 Insert() 方法未映射到 ProductsBLL 类中的方法(请记住,在配置数据源时将此映射设置为(None),请参见图3)。Since we want to use this DetailsView to add new products, we need to check the Enable Inserting option from the smart tag; however, there's no such option because the ObjectDataSource's Insert() method is not mapped to a method in the ProductsBLL class (recall that we set this mapping to (None) when configuring the data source see Figure 3).

若要配置 ObjectDataSource,请从其智能标记中选择 "配置数据源" 链接,启动向导。To configure the ObjectDataSource, select the Configure Data Source link from its smart tag, launching the wizard. 第一个屏幕允许您更改 ObjectDataSource 绑定到的基础对象;将其设置为 ProductsBLLThe first screen allows you to change the underlying object the ObjectDataSource is bound to; leave it set to ProductsBLL. 下一屏幕中列出了从 ObjectDataSource 的方法到基础对象的映射。The next screen lists the mappings from the ObjectDataSource's methods to the underlying object's. 即使我们明确指出,不应将 Insert()Delete() 方法映射到任何方法,如果您访问 "插入" 和 "删除" 选项卡,您将看到映射存在。Even though we explicitly indicated that the Insert() and Delete() methods should not be mapped to any methods, if you go to the INSERT and DELETE tabs you'll see that a mapping is there. 这是因为 ProductsBLLAddProductDeleteProduct 方法使用 DataObjectMethodAttribute 特性来指示它们分别是 Insert()Delete()的默认方法。This is because the ProductsBLL's AddProduct and DeleteProduct methods use the DataObjectMethodAttribute attribute to indicate that they are the default methods for Insert() and Delete(), respectively. 因此,在每次运行向导时,ObjectDataSource 向导会选择这些值,除非显式指定了某些其他值。Hence, the ObjectDataSource wizard selects these each time you run the wizard unless there's some other value explicitly specified.

保留指向 AddProduct 方法的 Insert() 方法,但再次将 "删除" 选项卡的下拉列表设置为 "(无)"。Leave the Insert() method pointing to the AddProduct method, but again set the DELETE tab's drop-down list to (None).

将 "插入" 选项卡的下拉列表设置为 AddProduct 方法Set the INSERT Tab's Drop-Down List to the AddProduct Method

图 14:将 "插入" 选项卡的下拉列表设置为 AddProduct 方法(单击以查看完全大小的图像Figure 14: Set the INSERT Tab's Drop-Down List to the AddProduct Method (Click to view full-size image)

将 "删除" 选项卡的下拉列表设置为 "(无)"Set the DELETE Tab's Drop-Down List to (None)

图 15:将 "删除" 选项卡的下拉列表设置为 "(无)" (单击查看完全大小的图像Figure 15: Set the DELETE Tab's Drop-Down List to (None) (Click to view full-size image)

进行这些更改后,ObjectDataSource 的声明性语法将展开以包括 InsertParameters 集合,如下所示:After making these changes, the ObjectDataSource's declarative syntax will be expanded to include an InsertParameters collection, as shown below:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    SelectMethod="GetProducts" TypeName="ProductsBLL"
    UpdateMethod="UpdateProduct" OnUpdating="ObjectDataSource1_Updating"
    InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
    <InsertParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="unitsInStock" Type="Int16" />
        <asp:Parameter Name="unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="reorderLevel" Type="Int16" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
    </InsertParameters>
</asp:ObjectDataSource>

重新运行已添加回 OldValuesParameterFormatString 属性的向导。Rerunning the wizard added back the OldValuesParameterFormatString property. 通过将此属性设置为默认值({0})或从声明性语法中删除它,花一些时间。Take a moment to clear this property by setting it to the default value ({0}) or removing it altogether from the declarative syntax.

借助提供插入功能的 ObjectDataSource,DetailsView 的智能标记现在将包括 "启用插入" 复选框;返回到设计器并选中此选项。With the ObjectDataSource providing inserting capabilities, the DetailsView's smart tag will now include the Enable Inserting checkbox; return to the Designer and check this option. 接下来,削减 DetailsView,使其只具有两个 BoundFields ProductName UnitPrice 和 CommandField。Next, pare down the DetailsView so that it only has two BoundFields - ProductName and UnitPrice - and the CommandField. 此时,DetailsView 的声明性语法应该如下所示:At this point the DetailsView's declarative syntax should look like:

<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
    <Fields>
        <asp:BoundField DataField="ProductName"
          HeaderText="ProductName" SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
          SortExpression="UnitPrice" />
        <asp:CommandField ShowInsertButton="True" />
    </Fields>
</asp:DetailsView>

图16此时会显示此页。Figure 16 shows this page when viewed through a browser at this point. 如您所见,DetailsView 列出了第一个产品的名称和价格(Chai)。As you can see, the DetailsView lists the name and price of the first product (Chai). 但我们想要的是一个插入接口,该接口为用户提供了一种将新产品快速添加到数据库的方法。What we want, however, is an inserting interface that provides a means for the user to quickly add a new product to the database.

DetailsView 当前以只读模式呈现The DetailsView is Currently Rendered in Read-Only Mode

图 16: DetailsView 当前呈现为只读模式(单击以查看完全大小的图像Figure 16: The DetailsView is Currently Rendered in Read-Only Mode (Click to view full-size image)

若要在插入模式下显示 DetailsView,需要将 DefaultMode 属性设置为 InsertingIn order to show the DetailsView in its inserting mode we need to set the DefaultMode property to Inserting. 这会在首次访问时在插入模式下呈现 DetailsView,并在插入新记录后将其保存。This renders the DetailsView in insert mode when first visited and keeps it there after inserting a new record. 如图17所示,此类 DetailsView 提供了一个用于添加新记录的快速界面。As Figure 17 shows, such a DetailsView provides a quick interface for adding a new record.

DetailsView 提供了一个界面,用于快速添加新产品The DetailsView Provides an Interface for Quickly Adding a New Product

图 17: DetailsView 提供了一个界面,用于快速添加新产品(单击以查看完全大小的映像Figure 17: The DetailsView Provides an Interface for Quickly Adding a New Product (Click to view full-size image)

当用户输入产品名称和价格(例如,如图17所示的 "Acme 水源" 和1.99)并单击 "插入" 时,将在添加到数据库中的新产品记录中 culminating 回发可以和插入工作流开始。When the user enters a product name and price (such as "Acme Water" and 1.99, as in Figure 17) and clicks Insert, a postback ensues and the inserting workflow commences, culminating in a new product record being added to the database. DetailsView 维护其插入界面,GridView 会自动重新绑定到其数据源,以便包括新产品,如图18所示。The DetailsView maintains its inserting interface and the GridView is automatically rebound to its data source in order to include the new product, as shown in Figure 18.

产品

图 18:已将产品 "Acme 水源" 添加到数据库中Figure 18: The Product "Acme Water" Has Been Added to the Database

虽然图18中的 GridView 并未显示,但从 DetailsView 界面 CategoryIDSupplierIDQuantityPerUnit等的产品字段被分配 NULL 数据库值。While the GridView in Figure 18 doesn't show it, the product fields lacking from the DetailsView interface CategoryID, SupplierID, QuantityPerUnit, and so on are assigned NULL database values. 可以通过执行以下步骤来查看此内容:You can see this by performing the following steps:

  1. 在 Visual Studio 中转到服务器资源管理器Go to the Server Explorer in Visual Studio
  2. 展开 NORTHWND.MDF 数据库节点Expanding the NORTHWND.MDF database node
  3. 右键单击 "Products 数据库" 表节点Right-click on the Products database table node
  4. 选择 "显示表数据"Select Show Table Data

这将列出 Products 表中的所有记录。This will list all of the records in the Products table. 如图19所示,除 ProductIDProductNameUnitPrice 之外的所有新产品列都具有 NULL 值。As Figure 19 shows, all of our new product's columns other than ProductID, ProductName, and UnitPrice have NULL values.

未在 DetailsView 中提供的产品字段分配 NULL 值The Product Fields Not Provided in the DetailsView are Assigned NULL Values

图 19: DetailsView 中未提供的产品字段分配 NULL 值(单击以查看完全大小的图像Figure 19: The Product Fields Not Provided in the DetailsView are Assigned NULL Values (Click to view full-size image)

对于一个或多个此类值,我们可能需要提供除 NULL 以外的默认值,因为 NULL 不是最佳的默认选项,或者数据库列本身不允许 NULLWe may want to provide a default value other than NULL for one or more of these column values, either because NULL isn't the best default option or because the database column itself doesn't allow NULL s. 为实现此目的,我们可以通过编程方式设置 DetailsView 的 InputParameters 集合的参数值。To accomplish this we can programmatically set the values of the parameters of the DetailsView's InputParameters collection. 可在 DetailsView 的 ItemInserting 事件的事件处理程序或 ObjectDataSource 的 Inserting 事件的事件处理程序中完成此分配。This assignment can be done either in the event handler for the DetailsView's ItemInserting event or the ObjectDataSource's Inserting event. 由于我们已经了解了如何在数据 Web 控件级别使用前期事件和后级事件,因此让我们来探索这次使用 ObjectDataSource 的事件。Since we've already looked at using the pre- and post-level events at the data Web control level, let's explore using the ObjectDataSource's events this time.

步骤4:为CategoryIDSupplierID参数赋值Step 4: Assigning Values to theCategoryIDandSupplierIDParameters

在本教程中,我们假设你在通过此接口添加新产品时,为应用程序分配了一个 CategoryID,并 SupplierID 值为1。For this tutorial let's imagine that for our application when adding a new product through this interface it should be assigned a CategoryID and SupplierID value of 1. 如前文所述,ObjectDataSource 包含一对在数据修改过程中激发的前期和后级事件。As mentioned earlier, the ObjectDataSource has a pair of pre- and post-level events that fire during the data modification process. 调用 Insert() 方法时,ObjectDataSource 首先引发其 Inserting 事件,然后调用其 Insert() 方法已映射到的方法,并最终引发 Inserted 事件。When its Insert() method is invoked, the ObjectDataSource first raises its Inserting event, then calls the method that its Insert() method has been mapped to, and finally raises the Inserted event. Inserting 事件处理程序为我们的最后一次机会来调整输入参数或取消操作。The Inserting event handler affords us one last opportunity to tweak the input parameters or cancel the operation outright.

Note

在实际应用程序中,你可能希望让用户指定类别和供应商,或者根据某些条件或业务逻辑为其选择此值(而不是盲目选择 ID 1)。In a real-world application you would likely want to either let the user specify the category and supplier or would pick this value for them based on some criteria or business logic (rather than blindly selecting an ID of 1). 无论如何,此示例演示如何以编程方式设置 ObjectDataSource 的前期事件的输入参数的值。Regardless, the example illustrates how to programmatically set the value of an input parameter from the ObjectDataSource's pre-level event.

请花片刻时间为 ObjectDataSource 的 Inserting 事件创建事件处理程序。Take a moment to create an event handler for the ObjectDataSource's Inserting event. 请注意,事件处理程序的第二个输入参数是类型 ObjectDataSourceMethodEventArgs的对象,它具有用于访问参数集合(InputParameters)的属性和用于取消操作的属性(Cancel)。Notice that the event handler's second input parameter is an object of type ObjectDataSourceMethodEventArgs, which has a property to access the parameters collection (InputParameters) and a property to cancel the operation (Cancel).

protected void ObjectDataSource1_Inserting
    (object sender, ObjectDataSourceMethodEventArgs e)
{

}

此时,InputParameters 属性包含具有从 DetailsView 分配的值的 ObjectDataSource 的 InsertParameters 集合。At this point, the InputParameters property contains the ObjectDataSource's InsertParameters collection with the values assigned from the DetailsView. 若要更改其中一个参数的值,只需使用: e.InputParameters["paramName"] = valueTo change the value of one of these parameters, simply use: e.InputParameters["paramName"] = value. 因此,若要将 CategoryIDSupplierID 设置为值1,请调整 Inserting 事件处理程序,使其类似于以下内容:Therefore, to set the CategoryID and SupplierID to values of 1, adjust the Inserting event handler to look like the following:

protected void ObjectDataSource1_Inserting
    (object sender, ObjectDataSourceMethodEventArgs e)
{
    e.InputParameters["CategoryID"] = 1;
    e.InputParameters["SupplierID"] = 1;
}

这次添加新产品(如 Acme Soda)时,新产品的 CategoryIDSupplierID 列都设置为1(请参阅图20)。This time when adding a new product (such as Acme Soda), the CategoryID and SupplierID columns of the new product are set to 1 (see Figure 20).

新产品现在将其类别 Id 和供应商值设置为1New Products Now Have Their CategoryID and SupplierID Values Set to 1

图 20:新产品现在的 CategoryIDSupplierID 值设置为1(单击查看完全大小的图像Figure 20: New Products Now Have Their CategoryID and SupplierID Values Set to 1 (Click to view full-size image)

摘要Summary

在编辑、插入和删除进程的过程中,数据 Web 控件和 ObjectDataSource 都继续经历多个前期和后期级别事件。During the editing, inserting, and deleting process, both the data Web control and the ObjectDataSource proceed through a number of pre- and post-level events. 在本教程中,我们检查了预级别事件,并了解了如何使用这些事件来自定义输入参数或从数据 Web 控件和 ObjectDataSource 的事件中取消数据修改操作。In this tutorial we examined the pre-level events and saw how to use these to customize the input parameters or cancel the data modification operation altogether both from the data Web control and ObjectDataSource's events. 在下一教程中,我们将介绍如何创建和使用事件处理程序来执行后级事件。In the next tutorial we'll look at creating and using event handlers for the post-level events.

很高兴编程!Happy Programming!

关于作者About the Author

Scott Mitchell,创始人的4GuysFromRolla.com,已在使用 Microsoft Web 技术,自1998开始。Scott Mitchell, author of seven ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott 的工作方式是独立的顾问、培训师和撰稿人。Scott works as an independent consultant, trainer, and writer. 他的最新书籍是,在24小时内,sam ASP.NET 2.0His latest book is Sams Teach Yourself ASP.NET 2.0 in 24 Hours. 可以mitchell@4GuysFromRolla.com访问。He can be reached at mitchell@4GuysFromRolla.com. 或通过他的博客,可以在http://ScottOnWriting.NET找到。or via his blog, which can be found at http://ScottOnWriting.NET.

特别感谢Special Thanks To

此教程系列由许多有用的审阅者查看。This tutorial series was reviewed by many helpful reviewers. 本教程的主管评审者是 Jackie Goor 和 Liz Shulok。Lead reviewers for this tutorial were Jackie Goor and Liz Shulok. 想要查看我即将发布的 MSDN 文章?Interested in reviewing my upcoming MSDN articles? 如果是这样,请在mitchell@4GuysFromRolla.com放置一行If so, drop me a line at mitchell@4GuysFromRolla.com.