限制基于用户的数据修改功能 (VB)

作者 :Scott Mitchell

下载 PDF

在允许用户编辑数据的 Web 应用程序中,不同的用户帐户可能具有不同的数据编辑权限。 本教程介绍如何根据访问用户动态调整数据修改功能。

简介

许多 Web 应用程序支持用户帐户,并根据登录用户提供不同的选项、报告和功能。 例如,在我们的教程中,我们可能希望允许供应商公司的用户登录网站,并更新有关其产品的常规信息(可能每个单位的名称和数量)以及供应商信息,例如其公司名称、地址、联系人信息等。 此外,我们可能希望为公司人员提供一些用户帐户,以便他们可以登录并更新产品信息,例如库存单位、重新排序级别等。 我们的 Web 应用程序可能还允许匿名用户访问 (未登录) ,但会限制他们只查看数据。 有了这样的用户帐户系统,我们希望 ASP.NET 页面中的数据 Web 控件提供适合当前登录用户的插入、编辑和删除功能。

本教程介绍如何根据访问用户动态调整数据修改功能。 具体而言,我们将创建一个页面,用于在可编辑的 DetailsView 中显示供应商信息,以及列出供应商提供的产品的 GridView。 如果访问页面的用户来自我们公司,他们可以:查看任何供应商的信息;编辑他们的地址;并编辑供应商提供的任何产品的信息。 但是,如果用户来自特定公司,则他们只能查看和编辑自己的地址信息,并且只能编辑尚未标记为已停产的产品。

我们公司的用户可以编辑任何供应商的信息

图 1:我们公司的用户可以编辑任何供应商的信息 (单击以查看全尺寸图像)

特定供应商的用户只能查看和编辑其信息

图 2:特定供应商的用户只能查看和编辑其信息 (单击以查看全尺寸图像)

让我们开始吧!

注意

ASP.NET 2.0 s 成员资格系统提供了一个标准化、可扩展的平台,用于创建、管理和验证用户帐户。 由于成员资格制度的检查超出了这些教程的范围,因此本教程允许匿名访问者选择他们来自特定供应商还是来自我们的公司来“伪造”成员身份。 有关成员身份的详细信息,请参阅 检查 ASP.NET 2.0 s 成员资格、角色和配置文件 系列文章。

步骤 1:允许用户指定其访问权限

在实际 Web 应用程序中,用户帐户信息将包括他们是否为我们公司或特定供应商工作,一旦用户登录到网站,就可以从我们的 ASP.NET 页面以编程方式访问此信息。 此信息可以通过 ASP.NET 2.0 s 角色系统捕获,作为用户级帐户信息通过配置文件系统或通过某些自定义方式捕获。

由于本教程的目的是演示如何根据登录的用户调整数据修改功能,并且不是要展示 ASP.NET 2.0 秒的成员资格、角色和配置文件系统,我们将使用非常简单的机制来确定访问页面的用户的功能 - 一个 DropDownList,用户可以从中指示他们是否能够查看和编辑任何供应商信息,或者, 或者,他们可以查看和编辑哪些特定供应商的信息。 如果用户指示她可以查看和编辑 (默认) 的所有供应商信息,她可以翻页浏览所有供应商,编辑任何供应商的地址信息,并编辑所选供应商提供的任何产品的单位名称和数量。 但是,如果用户指示她只能查看和编辑特定供应商,则她只能查看该供应商的详细信息和产品,并且只能更新 那些未 停产产品的每个单位信息的名称和数量。

本教程的第一步是创建此 DropDownList,并使用系统中的供应商填充它。 UserLevelAccess.aspx打开 文件夹中的页面EditInsertDelete,添加属性设置为 SuppliersID DropDownList,并将此 DropDownList 绑定到名为 AllSuppliersDataSource的新 ObjectDataSource。

创建名为 AllSuppliersDataSource 的新 ObjectDataSource

图 3:创建名为 AllSuppliersDataSource 的新对象DataSource (单击以查看全尺寸图像)

由于我们希望此 DropDownList 包含所有供应商,因此请将 ObjectDataSource 配置为调用 SuppliersBLL 类的 GetSuppliers() 方法。 此外,请确保 ObjectDataSource 方法 Update() 映射到 SuppliersBLL 类 的 UpdateSupplierAddress 方法,因为此 ObjectDataSource 也将由我们将在步骤 2 中添加的 DetailsView 使用。

完成 ObjectDataSource 向导后,通过配置 Suppliers DropDownList 来完成这些步骤,使其显示 CompanyName 数据字段并将 SupplierID 数据字段用作每个 ListItem的值。

将 Suppliers DropDownList 配置为使用 CompanyName 和 SupplierID 数据字段

图 4:将 Suppliers DropDownList 配置为使用 CompanyNameSupplierID 数据字段 (单击 以查看全尺寸图像)

此时,DropDownList 列出了数据库中供应商的公司名称。 但是,我们还需要在 DropDownList 中包含“显示/编辑所有供应商”选项。 为此,请将 Suppliers DropDownList 的 AppendDataBoundItems 属性设置为 true ,然后添加 ListItemText 属性为“显示/编辑所有供应商”且值为 -1的 。 这可以直接通过声明性标记或通过Designer添加,方法是转到属性窗口并单击 DropDownList 属性Items中的省略号。

注意

有关将“全选”项添加到数据绑定 DropDownList 的更详细讨论,请参阅 使用 DropDownList 筛选母版/详细信息筛选 教程。

AppendDataBoundItems设置 属性并ListItem添加 后,DropDownList 的声明性标记应如下所示:

<asp:DropDownList ID="Suppliers" runat="server" AppendDataBoundItems="True"
    DataSourceID="AllSuppliersDataSource" DataTextField="CompanyName"
    DataValueField="SupplierID">
    <asp:ListItem Value="-1">Show/Edit ALL Suppliers</asp:ListItem>
</asp:DropDownList>

图 5 显示了通过浏览器查看时当前进度的屏幕截图。

“供应商”下拉列表包含一个 Show ALL ListItem,每个供应商加一个

图 5Suppliers DropDownList 包含“全部 ListItem显示”, (单击可查看全尺寸图像)

由于我们希望在用户更改其选择后立即更新用户界面,因此请将 Suppliers DropDownList 的 AutoPostBack 属性设置为 true。 在步骤 2 中,我们将创建一个 DetailsView 控件,该控件将根据 DropDownList 选择显示供应商 () 的信息。 然后,在步骤 3 中,我们将为此 DropDownList 事件 SelectedIndexChanged 创建事件处理程序,在该事件中,我们将添加基于所选供应商将相应供应商信息绑定到 DetailsView 的代码。

步骤 2:添加 DetailsView 控件

让我们使用 DetailsView 显示供应商信息。 对于可以查看和编辑所有供应商的用户,DetailsView 将支持分页,允许用户一次单步执行一条记录的供应商信息。 但是,如果用户为特定供应商工作,则 DetailsView 将仅显示该特定供应商的信息,并且不包含分页界面。 在任一情况下,DetailsView 都需要允许用户编辑供应商地址、城市和国家/地区字段。

将 DetailsView 添加到 DropDownList 下的 Suppliers 页面,将其 ID 属性设置为 SupplierDetails,并将其绑定到 AllSuppliersDataSource 在上一步中创建的 ObjectDataSource。 接下来,检查 DetailsView s 智能标记中的“启用分页”和“启用编辑”复选框。

注意

如果在 DetailsView 的智能标记中未看到“启用编辑”选项,则它是因为未将 ObjectDataSource 方法 Update() 映射到 SuppliersBLL 类 s UpdateSupplierAddress 方法。 请花点时间返回进行此配置更改,之后,“启用编辑”选项应显示在 DetailsView 的智能标记中。

SuppliersBLL由于 类方法UpdateSupplierAddress只接受四个参数 - supplierIDcityaddresscountry - 修改 DetailsView 的 BoundFields,使 CompanyNamePhone BoundFields 是只读的。 此外,请完全删除 SupplierID BoundField。 最后, AllSuppliersDataSource ObjectDataSource 当前将其 OldValuesParameterFormatString 属性设置为 original_{0}。 请花点时间从声明性语法中删除此属性设置,或将其设置为默认值 {0}

配置 SupplierDetails DetailsView 和 AllSuppliersDataSource ObjectDataSource 后,我们将具有以下声明性标记:

<asp:ObjectDataSource ID="AllSuppliersDataSource" runat="server"
    SelectMethod="GetSuppliers" TypeName="SuppliersBLL"
    UpdateMethod="UpdateSupplierAddress">
    <UpdateParameters>
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="address" Type="String" />
        <asp:Parameter Name="city" Type="String" />
        <asp:Parameter Name="country" Type="String" />
    </UpdateParameters>
</asp:ObjectDataSource>
<asp:DetailsView ID="SupplierDetails" runat="server" AllowPaging="True"
    AutoGenerateRows="False" DataKeyNames="SupplierID"
    DataSourceID="AllSuppliersDataSource">
    <Fields>
        <asp:BoundField DataField="CompanyName" HeaderText="Company"
            ReadOnly="True" SortExpression="CompanyName" />
        <asp:BoundField DataField="Address" HeaderText="Address"
            SortExpression="Address" />
        <asp:BoundField DataField="City" HeaderText="City"
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country"
            SortExpression="Country" />
        <asp:BoundField DataField="Phone" HeaderText="Phone" ReadOnly="True"
            SortExpression="Phone" />
        <asp:CommandField ShowEditButton="True" />
    </Fields>
</asp:DetailsView>

此时,无论在 DropDownList 中 Suppliers 所做的选择如何,都可以对 DetailsView 进行分页,并且可以更新所选供应商的地址信息 (请参阅图 6) 。

可以查看任何供应商信息,并更新其地址

图 6:可以查看任何供应商信息,并更新其地址 (单击以查看全尺寸图像)

步骤 3:仅显示所选供应商的信息

我们的页面当前显示所有供应商的信息,无论是否已从 Suppliers DropDownList 中选择特定供应商。 为了仅显示所选供应商的供应商信息,我们需要将另一个 ObjectDataSource 添加到页面,用于检索有关特定供应商的信息。

将新的 ObjectDataSource 添加到页面,将其 SingleSupplierDataSource命名为 。 在其智能标记中,单击“配置数据源”链接,并使其使用 SuppliersBLL 类 s GetSupplierBySupplierID(supplierID) 方法。 与 AllSuppliersDataSource ObjectDataSource 一样,将 SingleSupplierDataSource ObjectDataSource 的 Update() 方法映射到 SuppliersBLL 类的 UpdateSupplierAddress 方法。

将 SingleSupplierDataSource 对象DataSource 配置为使用 GetSupplierBySupplierID (supplierID) 方法

图 7:将 SingleSupplierDataSource ObjectDataSource 配置为使用 GetSupplierBySupplierID(supplierID) 方法 (单击以查看全尺寸图像)

接下来,我们再次提示指定方法输入参数的参数supplierIDGetSupplierBySupplierID(supplierID)。 由于我们想要显示从 DropDownList 中选择的供应商的信息,因此请使用 Suppliers DropDownList 属性 SelectedValue 作为参数源。

使用 Suppliers DropDownList 作为 supplierID 参数源

图 8:使用 Suppliers DropDownList 作为 supplierID 参数源 (单击以查看全尺寸图像)

即使添加了第二个 ObjectDataSource,DetailsView 控件当前也配置为始终使用 AllSuppliersDataSource ObjectDataSource。 我们需要根据所选的 DropDownList 项添加逻辑来调整 DetailsView Suppliers 使用的数据源。 为此,请为 Suppliers DropDownList 创建 SelectedIndexChanged 事件处理程序。 通过双击Designer中的 DropDownList,可以最轻松地创建此列表。 此事件处理程序需要确定要使用的数据源,并且必须将数据重新绑定到 DetailsView。 这是使用以下代码实现的:

Protected Sub Suppliers_SelectedIndexChanged _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Suppliers.SelectedIndexChanged
    If Suppliers.SelectedValue = "-1" Then
        ' The "Show/Edit ALL" option has been selected
        SupplierDetails.DataSourceID = "AllSuppliersDataSource"
        ' Reset the page index to show the first record
        SupplierDetails.PageIndex = 0
    Else
        ' The user picked a particular supplier
        SupplierDetails.DataSourceID = "SingleSupplierDataSource"
    End If
    ' Ensure that the DetailsView and GridView are in read-only mode
    SupplierDetails.ChangeMode(DetailsViewMode.ReadOnly)
    ' Need to "refresh" the DetailsView
    SupplierDetails.DataBind()
End Sub

事件处理程序首先确定是否选择了“显示/编辑所有供应商”选项。 如果是,则它将 DetailsView 设置为 SupplierDetailsDataSourceIDAllSuppliersDataSource ,并通过将 属性设置为 PageIndex 0,将用户返回到供应商集中的第一条记录。 但是,如果用户已从 DropDownList 中选择了特定供应商,则 DetailsView 将 DataSourceID 分配给 SingleSuppliersDataSource。 无论使用哪种数据源,模式 SuppliersDetails 都会还原回只读模式,并且通过调用 SuppliersDetails 控件 方法 DataBind() 将数据重新绑定到 DetailsView。

完成此事件处理程序后,DetailsView 控件现在会显示所选供应商,除非选择了“显示/编辑所有供应商”选项,在这种情况下,可以通过分页界面查看所有供应商。 图 9 显示了选择了“显示/编辑所有供应商”选项的页面;请注意,存在分页界面,允许用户访问和更新任何供应商。 图 10 显示了选择了 Ma 毛森供应商的页面。 在这种情况下,只有马梅森的信息是可以查看和编辑的。

可以查看和编辑所有供应商信息

图 9:可以查看所有供应商信息 (单击以查看全尺寸图像)

只能查看和编辑所选供应商的信息

图 10:只能查看和编辑所选供应商的信息 (单击以查看全尺寸图像)

注意

在本教程中,DropDownList 和 DetailsView 控件 EnableViewState 都必须设置为 true (默认) ,因为必须跨回发记住 DropDownList 和 SelectedIndex DetailsView 属性 DataSourceID 的更改。

步骤 4:在可编辑的 GridView 中列出供应商产品

完成 DetailsView 后,下一步是包含可编辑的 GridView,其中列出了所选供应商提供的这些产品。 此 GridView 应仅 ProductName 允许编辑 和 QuantityPerUnit 字段。 此外,如果访问页面的用户来自特定供应商,则只允许更新 停产的产品。 为此,我们需要首先添加类 方法的ProductsBLL重载,该重载仅ProductID采用 、 ProductNameQuantityPerUnit 字段作为UpdateProducts输入。 我们已在众多教程中预先演练了此过程,因此让我们来看看此处的代码,该代码应添加到 ProductsBLL

<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Update, False)> _
Public Function UpdateProduct(ByVal productName As String, _
    ByVal quantityPerUnit As String, ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        ' no matching record found, return false
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If
    ' Update the product record
    Dim rowsAffected As Integer = Adapter.Update(product)
    ' Return true if precisely one row was updated, otherwise false
    Return rowsAffected = 1
End Function

创建此重载后,我们准备添加 GridView 控件及其关联的 ObjectDataSource。 向页面添加新的 GridView,将其 ID 属性设置为 ProductsBySupplier,并将其配置为使用名为 ProductsBySupplierDataSource的新 ObjectDataSource。 由于我们希望此 GridView 按所选供应商列出这些产品,因此请使用 ProductsBLLGetProductsBySupplierID(supplierID) 方法。 此外,将 Update() 方法映射到刚刚创建的新 UpdateProduct 重载。

将 ObjectDataSource 配置为使用刚刚创建的 UpdateProduct 重载

图 11:将 ObjectDataSource 配置为使用 UpdateProduct 刚刚创建的重载 (单击以查看全尺寸图像)

我们再次提示选择方法输入supplierID参数的参数GetProductsBySupplierID(supplierID)源。 由于我们想要显示 DetailsView 中选择的供应商的产品,因此请使用 SuppliersDetails DetailsView 控件的 SelectedValue 属性作为参数源。

使用 SuppliersDetails DetailsView s SelectedValue 属性作为参数源

图 12:使用 SuppliersDetails DetailsView s SelectedValue 属性作为参数源 (单击以查看全尺寸图像)

返回到 GridView,删除除 、 QuantityPerUnitDiscontinued之外ProductName的所有 GridView 字段,将 Discontinued CheckBoxField 标记为只读。 此外,检查 GridView 智能标记中的“启用编辑”选项。 进行这些更改后,GridView 和 ObjectDataSource 的声明性标记应如下所示:

<asp:GridView ID="ProductsBySupplier" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ProductsBySupplierDataSource">
    <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
            SortExpression="QuantityPerUnit" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            ReadOnly="True" SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsBySupplierDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsBySupplierID" UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
    <SelectParameters>
        <asp:ControlParameter ControlID="SupplierDetails" Name="supplierID"
            PropertyName="SelectedValue" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

与以前的 ObjectDataSources 一 OldValuesParameterFormatString 样,此属性设置为 original_{0},这将导致在尝试更新每个单位的产品名称或数量时出现问题。 完全从声明性语法中删除此属性,或将其设置为默认值 {0}

完成此配置后,我们的页面现在列出了在 GridView 中选择的供应商提供的产品 (请参阅图 13) 。 目前,可以更新 每个单位的任何 产品名称或数量。 但是,我们需要更新页面逻辑,以便禁止与特定供应商关联的用户的停产产品使用此类功能。 我们将在步骤 5 中处理最后一部分。

显示所选供应商提供的产品

图 13:显示所选供应商提供的产品 (单击以查看全尺寸图像)

注意

添加此可编辑 GridView 后, Suppliers DropDownList 的 SelectedIndexChanged 事件处理程序应更新为将 GridView 返回到只读状态。 否则,如果在编辑产品信息时选择了其他供应商,则新供应商的 GridView 中的相应索引也将是可编辑的。 若要防止这种情况,只需在事件处理程序中SelectedIndexChanged将 GridView 的 EditIndex 属性设置为 -1

此外,请记住,必须启用 GridView 视图状态, (默认行为) 。 如果将 GridView 的 EnableViewState 属性设置为 false,则存在并发用户无意中删除或编辑记录的风险。

步骤 5:未选中“显示/编辑所有供应商”时,禁止编辑停产产品

ProductsBySupplier虽然 GridView 功能齐全,但它目前向来自特定供应商的用户授予了过多的访问权限。 根据我们的业务规则,此类用户不应能够更新停产的产品。 为了强制实施此功能,我们可以隐藏 (或禁用) 那些包含已停产产品的 GridView 行中的“编辑”按钮(当来自供应商的用户访问页面时)。

为 GridView 事件 RowDataBound 创建事件处理程序。 在此事件处理程序中,我们需要确定用户是否与特定供应商相关联,对于本教程,可以通过检查 Suppliers DropDownList 的 SelectedValue 属性来确定该属性 - 如果它不是 -1,则用户与特定供应商相关联。 对于此类用户,我们需要确定产品是否已停产。 我们可以通过 属性获取对绑定到 GridView 行的实际ProductRow实例的引用,如在 GridView 的页脚中显示摘要信息教程中所述。e.Row.DataItem 如果产品已停产,则可以使用上一教程 添加 Client-Side 删除时确认中所述的技术,获取对 GridView 命令字段中“编辑”按钮的编程引用。 获得引用后,可以隐藏或禁用该按钮。

Protected Sub ProductsBySupplier_RowDataBound _
    (ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _
    Handles ProductsBySupplier.RowDataBound
    If e.Row.RowType = DataControlRowType.DataRow Then
        ' Is this a supplier-specific user?
        If Suppliers.SelectedValue <> "-1" Then
            ' Get a reference to the ProductRow
            Dim product As Northwind.ProductsRow = _
                CType(CType(e.Row.DataItem, System.Data.DataRowView).Row, _
                Northwind.ProductsRow)
            ' Is this product discontinued?
            If product.Discontinued Then
                ' Get a reference to the Edit LinkButton
                Dim editButton As LinkButton = _
                    CType(e.Row.Cells(0).Controls(0), LinkButton)
                ' Hide the Edit button
                editButton.Visible = False
            End If
        End If
    End If
End Sub

有了此事件处理程序后,当以特定供应商的用户身份访问此页面时,那些停产的产品不可编辑,因为这些产品的“编辑”按钮是隐藏的。 例如,Chef Anton s Gumbo Mix 是新奥尔良卡琼喜悦供应商的停产产品。 访问此特定供应商的页面时,此产品的“编辑”按钮隐藏 (见图 14) 。 但是,在使用“显示/编辑所有供应商”访问时,可以使用“编辑”按钮 (见图 15) 。

对于 Supplier-Specific 用户,Chef Anton 的“编辑”按钮已隐藏

图 14:对于 Supplier-Specific 用户,Chef Anton s Gumbo Mix 的“编辑”按钮处于隐藏状态 (单击以查看全尺寸图像)

对于“显示/编辑所有供应商用户”,将显示 Chef Anton s Gumbo Mix 的“编辑”按钮

图 15:对于“显示/编辑所有供应商用户”,“Chef Anton s Gumbo Mix”的“编辑”按钮显示在 (单击以查看全尺寸图像)

在业务逻辑层中检查访问权限

在本教程中,ASP.NET 页处理有关用户可以看到哪些信息以及他可以更新哪些产品的所有逻辑。 理想情况下,此逻辑也会出现在业务逻辑层。 例如,SuppliersBLL返回所有供应商的类方法 GetSuppliers() () 可能包含检查,以确保当前登录的用户与特定供应商相关联。 同样,UpdateSupplierAddress该方法可以包含一个检查,以确保当前登录的用户为我们公司 (工作,因此可以更新所有供应商地址信息) 或与正在更新数据的供应商相关联。

此处未包含此类 BLL 层检查,因为在我们的教程中,用户权限由页面上的 DropDownList 决定,BLL 类无法访问该列表。 使用成员身份系统或 ASP.NET (提供的开箱即用身份验证方案之一(例如Windows 身份验证) )时,可以从 BLL 访问当前登录的用户信息和角色信息,从而使此类访问权限检查在表示层和 BLL 层都成为可能。

总结

提供用户帐户的大多数站点都需要基于登录的用户自定义数据修改界面。 管理用户可能能够删除和编辑任何记录,而非管理用户可能仅限于更新或删除自己创建的记录。 无论方案是什么,都可以扩展数据 Web 控件、ObjectDataSource 和业务逻辑层类,以基于登录用户添加或拒绝某些功能。 在本教程中,我们了解了如何根据用户是否与特定供应商关联或是否为我们公司工作来限制可查看和可编辑的数据。

本教程总结了如何使用 GridView、DetailsView 和 FormView 控件插入、更新和删除数据。 从下一教程开始,我们将把注意力转向添加分页和排序支持。

编程愉快!

关于作者

Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。