基于用户来限制数据修改功能23

简介

很多 Web 应用程序都支持设立用户帐户,并根据登录用户的不同,提供不同的选项、报表和功能。例如,在本教程所用的例子中,我们就可能允许来自供应商公司的用户登录到网站并更新他们产品的一般信息,比如产品名称和单位数量,以及供应商本身的信息,如公司名、地址、联系人等。此外,我们可能还想提供给我们公司的员工一些用户帐户,他们可以登录上来修改产品信息如库存量、再订货水平等。我们的Web 应用程序可能还允许匿名用户访问,即那些没有登录的访问者,但限制他们只能浏览数据。建立好这样的用户帐户系统后,我们还想让我们的ASP.NET 网页上的 Web 数据控件为正确登录的用户提供适宜的新增、修改和删除功能。

本教程中,我们将了解怎样动态地调整基于访问用户的数据修改权限。具体来说,我们将创建一个网页,在一个可编辑的DetailsView 中显示供应商信息,并用一个 GridView 列出该供应商所提供的产品。如果是我们公司的用户访问该网页,他们能够:查阅任一供应商的信息,修改其地址,并修改该供应商提供的所有产品的信息。但是,如果用户是来自某一特定公司,他们则只能查阅和修改他们自己的地址,只能修改他们公司没有标上“断货”的产品信息。

图1:我们公司的用户可以修改所有供应商的信息

图2:来自某一特定公司的用户只能查阅和修改他们自己的信息

让我们开始吧 !

注意:ASP.NET 2.0 的用户身份管理系统提供了一个标准的、可扩展的平台,可用于创建、管理和验证用户帐户。由于对用户身份的检查超出了本教程的范围,所以在本教程中我们允许匿名访问者“伪造”身份,由他们自己选择是来自某一特定公司还是来自我们公司。有关用户身份的更多信息,参阅我写的 Examining ASP.NET 2.0’s Membership, Roles, and Profile 系列文章。

步骤1 :让用户设定自己的访问权限

在实际使用的 Web 应用程序中,一个用户的帐户信息中包括他是来自我们公司还是来自某一特定供应商的信息,而且只要该用户一登录我们网站,我们的ASP.NET 网页就能通过程序获得这些信息。获得这些信息,可以通过ASP.NET 2.0 的角色系统,或是通过档案系统中的用户级别帐户信息,或是其他自定义手段。

因为本教程的目的是演示基于登录用户的数据修改功能,而不是展示ASP.NET 2.0 的用户身份、角色和档案系统,所以我们将使用一个非常简单的机制来确定用户访问网页的功能:用一个DropDownList (下拉框),让用户在其中自己选择他们是否能够查阅和修改所有供应商的信息,或是选择他们能够查阅和修改哪个特定供应商信息。如果用户选择他能够查阅和修改所有供应商的信息(默认值),他就可以逐页浏览所有供应商,修改任一供应商的地址信息,并修改所选择的的供应商的任一产品的名称和单位数量。但是,如果用户选择了他只能查阅和修改某一特定供应商的信息,他就只能查阅该供应商的详细信息和产品信息,只能修改那些没有断货的产品的名称和单位数量信息。

本教程的第一步,是创建该 DropDownList ,并载入系统中的供应商信息。打开EditInsertDelete 文件夹里的UserLevelAccess.aspx 页面,添加一个 DropDownList ,设其 ID 属性为 Suppliers ,并将该 DropDownList 绑定到一个新的 ObjectDataSource 上,命名为 AllSuppliersDataSource 。

图3 :创建一个新的ObjectDataSource 并命名为 AllSuppliersDataSource

因为我们想让该 DropDownList 包括所有供应商,配置 ObjectDataSource 调用 SuppliersBLL 类的 GetSuppliers() 方法。还要确认把 ObjectDataSource 的 Update() 方法指定为使用SuppliersBLL 类的 UpdateSupplierAddress 方法,因为我们将在步骤 2 中添加的DetailsView 也要使用该 ObjectDataSource 。

在 ObjectDataSource 向导运行完之后,还要做几步配置 Suppliers DropDownList 的工作,让其显示 CompanyName 数据字段,并用SupplierID 数据字段作为每个 ListItem 的值。

图4:配置Suppliers DropDownList 使用CompanyName 和 SupplierID 数据字段

这时,DropDownList 中列出了数据库里的供应商的公司名。但是,我们还需要加一个“Show/Edit ALL Suppliers” (显示/ 修改所有供应商)列表项到该DropDownList 的列表中。为做到此功能,要设置 Suppliers DropDownList 的 AppendDataBoundItems 属性为 true ,然后添加一个 ListItem ,其 Text 属性是“Show/Edit ALL Suppliers” ,赋值 -1 。添加的办法,或是直接通过声明式标记,或是通过Designer :到Properties 窗口单击 DropDownList 的 Items 属性。

注意:关于如何向绑定数据的 DropDownList 中添加 “Select All” 列表项的详细讨论,参阅前面的教程 使用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 是从浏览器看到的当前进度的截屏。

图5 :Suppliers DropDownList 中包含一个 “Show/Edit ALL Suppliers” 列表项,和所有供应商的列表项

因为我们想在用户改变了他们的选择之后立即就更新用户界面,所以设置Suppliers DropDownList 的 AutoPostBack 属性为 true 。在步骤2 我们将创建一个 DetailsView 控件,它将根据在该 DropDownList 里做的选择显示供应商信息。之后,在步骤3 ,我们将为该DropDownList 的 SelectedIndexChanged 事件创建一个 Event Handler ,在其中将加入代码,根据所选择的供应商将适当的供应商信息绑定到DetailsView 。

步骤2 :添加一个 DetailsView 控件

让我们用一个 DetailsView 来显示供应商信息。对于能够查阅和修改所有供应商信息的用户,该DetailsView 支持分页功能,允许用户翻页查阅供应商信息,每页一条记录。但是对于来自某一特定供应商的用户,该DetailsView 就只显示特定供应商的信息,不包括分页界面。在这两种情况下,该DetailsView 都允许用户修改供应商的地址、城市和国家字段。

添加一个 DetailsView 到页面中,放到Suppliers DropDownList 的下面,设置其 ID 属性为 SupplierDetails ,并将其绑定到在上一步骤中创建的 AllSuppliersDataSource ObjectDataSource 上。然后,在 DetailsView 的智能标记中勾选上 Enable Paging 和 Enable Editing 复选框。

注意:如果您在 DetailsView 的智能标记中没有看到一个 Enable Editing 选项,那是因为您没有把 ObjectDataSource 的 Update() 方法指定为使用SuppliersBLL 类的 UpdateSupplierAddress 方法。用一点时间回去修改该配置,然后在DetailsView 的智能标记上就会出现 Enable Editing 选项了。

因为 SuppliersBLL 类的 UpdateSupplierAddress 方法只接受四个参数――supplierID 、address 、city 和country ,修改 DetailsView 的 BoundFields 让 CompanyName 和 Phone 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>

现在,DetailsView 能够翻页了,而且不管Suppliers DropDownList 中的选择是什么,都能够修改所选择的供应商的地址信息(参见图6 )。

图6 :能够查阅任一供应商的信息并能修改其地址

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

我们的网页目前显示所有供应商的信息,而不论在Suppliers DropDownList 中是否选择了某一特定供应商。为了只显示所选择的供应商信息,我们需要向网页中再添加另一个ObjectDataSource ,用于获取一个特定供应商的信息。

向页面添加一个新的 ObjectDataSource ,将其命名为 SingleSupplierDataSource 。在其智能标记中单击 Configure Data Source 链接,并让其使用 SuppliersBLL 类的 GetSupplierBySupplierID(supplierID) 方法。像 AllSuppliersDataSource ObjectDataSource 一样,把 SingleSupplierDataSource ObjectDataSource 的 Update() 方法指定为使用 SuppliersBLL 类的 UpdateSupplierAddress 方法。

图7 :配置SingleSupplierDataSource ObjectDataSource 使用 GetSupplierBySupplierID(supplierID) 方法

然后,让我们指定 GetSupplierBySupplierID(supplierID) 方法的 supplierID 输入参数的参数源。因为我们想显示从DropDownList 中选择的供应商的信息,使用 Suppliers DropDownList 的SelectedValue 属性作为参数源。

图8 :使用Suppliers DropDownList 作为supplierID 参数源

虽然已经加进了这第二个 ObjectDataSource ,根据 DetailsView 控件目前的配置,仍然是使用 AllSuppliersDataSource ObjectDataSource 。我们需要加一段程序,调整该 DetailsView 使用的数据源,使其依赖于在 Suppliers DropDownList 中所选择的列表项。为实现该功能,为Suppliers DropDownList 创建一个 SelectedIndexChanged Event Handler 。通过在 Designer 中双击 DropDownList ,很容易便可创建。该 Event Handler 需要确定使用哪个数据源,并重新绑定数据到页面上的DetailsView 。用以下代码实现:

protected void Suppliers_SelectedIndexChanged(object sender, EventArgs e) 

    if (Suppliers.SelectedValue == "-1") 
    { 
        // 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"; 
 
    // Ensure that the DetailsView is in read-only mode 
    SupplierDetails.ChangeMode(DetailsViewMode.ReadOnly); 
 
    // Need to "refresh" the DetailsView 
    SupplierDetails.DataBind(); 
}

在该 Event Handler 的一开始,首先确定是否选择了 “Show/Edit ALL Suppliers” 。如果是,则设置 SupplierDetails DetailsView 的 DataSourceID 到 AllSuppliersDataSource ,并设置 PageIndex 属性为 0 ,把供应商集合中的第一条记录返回给用户。与此相反,如果用户在那个DropDownList 中选择了某一特定供应商,就将 DetailsView 的 DataSourceID 设置到 SingleSuppliersDataSource 。无论使用哪个数据源,都把该 SuppliersDetails 恢复到只读模式,而且通过调用 SuppliersDetails 控件的 DataBind() 方法,将数据重新绑定到DetailsView 。

在写好该 Event Handler 以后,现在 DetailsView 控件只显示所选择的供应商,除非选择了“Show/Edit ALL Suppliers” 选项;在后一种情况下,可以通过翻页功能浏览所有供应商的信息。图9 显示了选择“Show/Edit ALL Suppliers” 选项后的页面,注意它具有翻页功能,允许用户查阅和更新任一供应商的信息。图10 显示的是选择了供应商 Ma Maison 后的页面。在这种情况下,只有 Ma Maison 的信息是可见的和可编辑的。

图9 :所有供应商的信息都可以查阅和编辑

图10 :只有所选择的供应商信息可以查阅和编辑

注意:对于本教程所用的例子,DropDownList 和 DetailsView 控件的EnableViewState 都必须设置为 true (默认值),因为 DropDownList 的 SelectedIndex 和 DetailsView 的DataSourceID 属性的变化必须在回传过程中记住。

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

当 DetailsView 设置完以后,我们的下一步是用一个可编辑的GridView 来列出所选择的供应商的产品信息。该GridView 应该只允许修改 ProductName 和 QuantityPerUnit 字段。此外,如果访问此页的用户是来自某一特定供应商,他只能修改那些没有断货的产品信息。为做到这些,我们需要首先添加一个ProductsBLL 类的 UpdateProducts 方法的重载,只将 ProductID 、ProductName 和 QuantityPerUnit 字段作为输入参数。我们在以前的几部教程中都已经详细讨论过该过程,所以我们在这里只看一下应该加到ProductsBLL 中的代码:

[System.ComponentModel.DataObjectMethodAttribute( 
System.ComponentModel.DataObjectMethodType.Update, false)] 
public bool UpdateProduct(string productName, string quantityPerUnit, 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 (quantityPerUnit == null) 
        product.SetQuantityPerUnitNull(); 
    else 
        product.QuantityPerUnit = quantityPerUnit; 
 
    // Update the product record 
    int rowsAffected = Adapter.Update(product); 
 
    // Return true if precisely one row was updated, otherwise false 
    return rowsAffected == 1; 
}

建立好重载以后,我们就可以着手添加GridView 控件和相关的 ObjectDataSource 了。向页面加入一个新的 GridView ,设置 ID 属性至 ProductsBySupplier ,并配置其使用新的名为 ProductsBySupplierDataSource 的 ObjectDataSource 。因为我们想让该 GridView 列出所选择的供应商的产品信息,使用ProductsBLL 类的GetProductsBySupplierID(supplierID) 方法。也把 Update() 方法指定为使用我们刚刚创建的,新的UpdateProduct 重载。

图11 :配置ObjectDataSource 使用刚创建的UpdateProduct 重载

接下来提示我们选择 GetProductsBySupplierID(supplierID) 方法的 supplierID 输入参数的参数源。因为我们想要显示选到DetailsView 中的那个供应商的产品信息,使用 SuppliersDetails DetailsView 控件的 SelectedValue 属性作为参数源。

图12 :使用SuppliersDetails DetailsView 的SelectedValue 属性作为参数源

让我们回到 GridView ,删除 GridView 中所有的其他字段只留下 ProductName 、QuantityPerUnit 和 Discontinued ,把 Discontinued 的 CheckBoxField 设为只读。并且,在该 GridView 的智能标记中勾选上 Enable Editing 选项。在做完这些修改之后,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 Event Handler 需要修改,将该 GridView 返回到一个只读状态。否则的话,如果在产品信息修改尚未完成时又选择了另一供应商,在GridView 中新的供应商的相应指标也是可编辑的了。为防止这一现象,在SelectedIndexChanged Event Handler 中把 GridView 的 EditIndex 属性设为 -1 即可。

另外重申一下很重要的一点,必须启用GridView 的视图状态(默认设置)。如果将 GridView 的 EnableViewState 属性设置为 false ,则可能存在让多个用户无意中同时删除或修改记录的风险。有关更多信息,请参阅 WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 。

步骤5 :当没有选择“Show/Edit ALL Suppliers” 时不允许修改断货的产品记录

尽管 ProductsBySupplier GridView 已经有了所有的功能,但它目前对来自某特定供应商的用户给予了太多的访问权限。根据我们的业务规则,这样的用户应该不能修改那些断货的产品信息。为执行该规则,当来自供应商的用户访问网页时,我们可以在GridView 那些有断货产品的行中隐藏(或禁用)Edit 按钮。

为 GridView 的 RowDataBound 事件创建一个 Event Handler 。在该 Event Handler 中,我们首先需要确定该用户是否与某一特定供应商有关系。在本教程的例子中,通过检查Suppliers DropDownList 的 SelectedValue 属性便可确定:如果值不等于 -1 ,那么该用户就是与某一特定供应商有关系。对这样的用户,我们还要确定产品是否断货。我们可以由e.Row.DataItem 属性获取一个引用到绑定到 GridView 行的 ProductRow 实例中,正如在教程 在GridView的脚注中显示小结信息 中所讨论过的。如果该产品断货,我们则可以通过程序获取一个引用到GridView 的 CommandField 里的 Edit 按钮;这一技巧在以前的教程 删除时添加客户端确认 中已经讨论过。一旦我们获得了该引用,我们就可以隐藏或禁用该按钮。

protected void ProductsBySupplier_RowDataBound(object sender, GridViewRowEventArgs e) 

    if (e.Row.RowType == DataControlRowType.DataRow) 
    { 
        // Is this a supplier-specific user? 
        if (Suppliers.SelectedValue != "-1") 
        { 
            // Get a reference to the ProductRow 
            Northwind.ProductsRow product = 
                (Northwind.ProductsRow)((System.Data.DataRowView)e.Row.DataItem).Row; 
 
            // Is this product discontinued? 
            if (product.Discontinued) 
            { 
                // Get a reference to the Edit LinkButton 
                LinkButton editButton = (LinkButton)e.Row.Cells[0].Controls[0]; 
 
                // Hide the Edit button 
                editButton.Visible = false; 
            } 
        } 
    } 
}

配置好该 Event Handler 之后,当来自某一特定供应商的用户访问网页时,那些已经断货的产品信息便不能再修改了,因为这些产品的Edit 按钮已经被隐藏起来。例如,Chef Anton’s Gumbo Mix 是New Orleans Cajun Delights 供应商的一个断货产品。当来自该供应商的用户访问该网页时,产品的Edit 按钮就隐藏了起来(参见图14 )。然而,当一个使用 “Show/Edit ALL Suppliers” 的用户访问网页时,该 Edit 按钮又可以使用了(参见图 15 )。

图14 :对来自特定供应商的用户,产品Chef Anton’s Gumbo Mix 的 Edit 按钮隐藏了起来

图15:对“Show/Edit ALL Suppliers” 用户,产品 Chef Anton’s Gumbo Mix 的 Edit 按钮又显示了出来

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

在本教程中,是由 ASP.NET 网页处理所有的逻辑关系,包括用户能够看到什么信息,能够修改哪些产品信息等。理想的情况是,该逻辑关系在业务逻辑层也应该得到体现。例如,SuppliersBLL 类的 GetSuppliers() 方法(它返回所有供应商)也应包括一个检查,确保当前登录的用户不与某一特定供应商有关系。同样,UpdateSupplierAddress 方法也应包括一个检查,确认当前登录的用户是我们公司的员工(这样他就能修改所有供应商的地址信息),还是与某一特定的供应商(其数据正在修改中)有关系。

我没有把业务逻辑层的检查包括在本教程中,是因为用户的权限是通过网页中的DropDownList 决定的,而业务逻辑层的类不能访问到它。当使用用户身份管理系统或ASP.NET 提供的现成的身份验证方案(如Windows 身份验证)时,当前登录的用户信息和角色信息能够从业务逻辑层访问到,所以在表现层和业务逻辑层做这样的访问权限检查都是可以做到的。

小结

大多数提供用户帐户的网站都需要定制基于登录用户的数据修改界面。管理员用户可以删除和修改任何一条记录,而非管理员用户则可能限于只能修改或删除他们自己创建的记录。无论是哪种情况,我们都可以通过扩展Web 数据控件、ObjectDataSource 和业务逻辑层类,来增加或禁用基于登录用户的某些功能。在本教程中,我们学到了如何根据用户是与某一特定供应商有关系,还是属于我们公司自己的员工,来确定对数据的可视和可编辑的限制。

到本教程为止,我们对使用 GridView 、DetailsView 和 FormView 控件新增、修改和删除数据的讨论将暂告一段落。从下一篇教程开始,我们将转向增加分页和排序功能。

快乐编程!

posted @ 2016-05-01 21:34  迅捷之风  阅读(178)  评论(0编辑  收藏  举报