作者 :斯科特·米切尔
在允许用户编辑数据的 Web 应用程序中,不同的用户帐户可能具有不同的数据编辑权限。 本教程介绍如何根据来访的用户动态调整数据修改功能。
介绍
许多 Web 应用程序支持用户帐户,并根据登录用户提供不同的选项、报表和功能。 例如,通过我们的教程,我们可能需要允许供应商公司的用户登录网站,并更新有关其产品的常规信息-其名称和数量(也许)以及供应商信息,例如其公司名称、地址、联系人信息等。 此外,我们可能希望包括公司人员的某些用户帐户,以便他们可以登录和更新产品信息,如库存单位、重新排序级别等。 我们的 Web 应用程序还可能允许匿名用户访问(尚未登录的人员),但会限制他们仅查看数据。 有了这样的用户帐户系统,我们希望 ASP.NET 页面中的数据 Web 控件提供适用于当前登录用户的插入、编辑和删除功能。
本教程介绍如何根据来访的用户动态调整数据修改功能。 具体而言,我们将创建一个页面,在可编辑的 DetailsView 中显示供应商的信息,并通过一个 GridView 列出该供应商提供的产品。 如果访问页面的用户来自我们公司,他们可以:查看任何供应商的信息;编辑其地址;并编辑供应商提供的任何产品的信息。 但是,如果用户来自特定公司,他们只能查看和编辑自己的地址信息,并且只能编辑尚未标记为已停用的产品。
图 1:公司用户可编辑任何供应商信息(单击可查看全尺寸图像)
图 2:来自特定供应商的用户只能查看和编辑其信息(单击以查看全尺寸图像)
让我们开始吧!
注释
ASP.NET 2.0 s 成员身份系统提供了一个标准化、可扩展的平台,用于创建、管理和验证用户帐户。 由于成员资格制度的检查超出了这些教程的范围,因此本教程通过允许匿名访问者选择是来自特定供应商还是来自我们公司的成员身份来“伪造”成员身份。 有关成员身份的更多信息,请参阅我撰写的 ASP.NET 2.0 用户成员资格、角色和配置文件解析 系列文章。
步骤 1:允许用户指定其访问权限
在实际 Web 应用程序中,用户帐户信息将包括他们是否为公司或特定供应商工作,并且一旦用户登录到网站,就可以以编程方式从我们的 ASP.NET 页面访问此信息。 此信息可以通过 ASP.NET 2.0 的角色系统捕获,例如用户级别帐户信息通过个人资料系统,或者通过一些自定义方式来捕获。
由于本教程的目的是为了展示如何根据已登录用户调整数据修改功能,并不打算展示 ASP.NET 2.0 的成员资格、角色和配置文件系统,因此我们将使用一个非常简单的机制来确定访问页面的用户的权限——一个下拉列表(DropDownList)。用户可以通过它指示是否能够查看和编辑所有供应商的信息,或者能够查看和编辑某个特定供应商的信息。 如果用户指示她可以查看和编辑所有供应商的相关信息(默认设置),她可以浏览所有供应商,编辑任何供应商的地址信息,并为所选供应商提供的任何产品编辑产品名称和每单位的数量。 但是,如果用户指示她只能查看和编辑特定供应商,则她只能查看该供应商的详细信息和产品,并且只能更新 未 停止使用的这些产品的每个单位信息的名称和数量。
本教程的第一步是创建一个下拉列表,并将系统中的供应商填充进列表中。 打开EditInsertDelete
文件夹中的UserLevelAccess.aspx
页面,添加一个 DropDownList,其ID
属性设置为Suppliers
,并将此 DropDownList绑定到名为AllSuppliersDataSource
的新 ObjectDataSource。
图 3:新建名为 AllSuppliersDataSource
ObjectDataSource (单击以查看全尺寸图像)
由于我们希望此 DropDownList 包含所有供应商,因此请将 ObjectDataSource 配置为调用 SuppliersBLL
类的 GetSuppliers()
方法。 此外,请确保 ObjectDataSource 的 Update()
方法映射到 SuppliersBLL
类的 UpdateSupplierAddress
方法,因为我们将在步骤 2 中添加的 DetailsView 控件也将使用此 ObjectDataSource。
完成 ObjectDataSource 向导后,通过配置 Suppliers
DropDownList 来完成这些步骤,以便显示 CompanyName
数据字段并使用 SupplierID
数据字段作为每个 ListItem
字段的值。
图 4:将 Suppliers
DropDownList 配置为使用 CompanyName
和 SupplierID
数据字段(单击以查看全尺寸图像)
此时,DropDownList 将列出数据库中供应商的公司名称。 但是,我们还需要将“显示/编辑所有供应商”选项包含在 DropDownList 中。 为此,请将Suppliers
DropDownList 的AppendDataBoundItems
属性设置为true
,然后添加一个ListItem
,其Text
属性为“显示/编辑所有供应商”,其值为-1
。 可以直接通过声明性标记或通过设计器添加此项,方法是转到“属性窗口”并单击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 显示了通过浏览器查看时当前进度的屏幕截图。
图 5:Suppliers
下拉列表包含一个显示所有供应商的选项,以及每个供应商的一个选项(单击以查看全尺寸图像)
由于我们想要在用户更改其选择后立即更新用户界面,因此请将 Suppliers
DropDownList s AutoPostBack
属性设置为 true
。 在步骤 2 中,我们将创建一个 DetailsView 控件,该控件将根据 DropDownList 选择显示供应商的信息。 然后,在步骤 3 中,我们将为此 DropDownList 事件 SelectedIndexChanged
创建事件处理程序,在此事件中,我们将添加代码,以基于所选供应商将相应的供应商信息绑定到 DetailsView。
步骤 2:添加 DetailsView 控件
让我们使用 DetailsView 来显示供应商信息。 对于可以查看和编辑所有供应商的用户,DetailsView 将支持分页功能,允许用户逐一查看供应商信息。 但是,如果用户针对特定供应商工作,则 DetailsView 将仅显示该特定供应商的信息,并且不包含分页界面。 在任一情况下,DetailsView 都需要允许用户编辑供应商的地址、城市和国家/地区字段。
在 DropDownList 下面的 Suppliers
页面上添加 DetailsView,将其 ID
属性设置为 SupplierDetails
,并将其绑定到上一步中创建的 AllSuppliersDataSource
ObjectDataSource。 接下来,选中 DetailsView 智能标记中的“启用分页”和“启用编辑”复选框。
注释
如果在 DetailsView 智能标记中看不到“启用编辑”选项,是因为未将 ObjectDataSource 方法 Update()
映射到 SuppliersBLL
类的方法 UpdateSupplierAddress
。 请花点时间返回并做出此配置更改,之后“启用编辑”选项应显示在 DetailsView 智能标记中。
由于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
。 从智能标记中,单击“配置数据源”链接,并使其使用 SuppliersBLL
类的 GetSupplierBySupplierID(supplierID)
方法。 与AllSuppliersDataSource
ObjectDataSource一样,SingleSupplierDataSource
ObjectDataSource的方法映射到SuppliersBLL
类的方法UpdateSupplierAddress
。
图 7:将 ObjectDataSource 配置为 SingleSupplierDataSource
使用 GetSupplierBySupplierID(supplierID)
方法(单击以查看全尺寸图像)
接下来,我们被提示指定GetSupplierBySupplierID(supplierID)
方法的supplierID
输入参数的参数源。 由于我们想要显示从 DropDownList 中选择的供应商的信息,因此请使用 Suppliers
DropDownList 的属性 SelectedValue
作为参数源。
图 8:使用 Suppliers
DropDownList 作为 supplierID
参数源(单击可查看全尺寸图像)
即使添加了第二个 ObjectDataSource,DetailsView 控件当前也配置为始终使用 AllSuppliersDataSource
ObjectDataSource。 我们需要添加逻辑来根据所选的 DropDownList 项调整 DetailsView Suppliers
使用的数据源。 为此,请为供应商 DropDownList 创建 SelectedIndexChanged
事件处理程序。 这可以通过双击设计器中的 DropDownList 轻松创建。 此事件处理程序需要确定要使用的数据源,并且必须将数据重新绑定到 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();
}
事件处理程序首先确定是否选择了“显示/编辑所有供应商”选项。 如果是,它将 DetailsView 设置为DataSourceID
AllSuppliersDataSource
,并通过将PageIndex
属性设置为 0,将用户返回到供应商集中的第一条记录。 但是,如果用户已从 DropDownList 中选择了特定供应商,则 DetailsView s DataSourceID
将 SingleSuppliersDataSource
分配给该供应商。 无论使用什么数据源,该 SuppliersDetails
模式都会还原回只读模式,并且通过调用 SuppliersDetails
控件 DataBind()
的方法将数据反弹到 DetailsView。
有了此事件处理程序,DetailsView 控件现在会显示所选供应商,除非选择了“显示/编辑所有供应商”选项,在这种情况下,所有供应商都可以通过分页界面查看。 图 9 显示选择了“显示/编辑所有供应商”选项的页面;请注意,分页界面存在,允许用户访问和更新任何供应商。 图 10 显示了选择了马工厂供应商的页面。 在这种情况下,只有Ma Maison的信息是可查看和编辑的。
图 9:可以查看和编辑所有供应商信息(单击可查看全尺寸图像)
图 10:只能查看和编辑所选供应商的信息(单击以查看全尺寸图像)
注释
在本教程中,DropDownList 和 DetailsView 控件 EnableViewState
都必须设置为 true
(默认值),因为 DropDownList s 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 s SelectedValue
属性作为参数源(单击以查看全尺寸图像)
返回到 GridView,删除除 ProductName
、QuantityPerUnit
和 Discontinued
以外的所有 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
创建事件处理程序。 在此事件处理程序中,我们需要确定用户是否与特定供应商相关联,对于本教程,可以通过检查供应商 DropDownList s SelectedValue
属性(如果不是 -1)来确定用户是否与特定供应商相关联。 对于此类用户,我们随后需要确定产品是否停止使用。 我们可以通过 e.Row.DataItem
属性获取对绑定到 GridView 行的实际 ProductRow
实例的引用,如在教程《在 GridView 的页脚中显示摘要信息》中所述。 如果产品已停止使用,则可以使用上一教程中讨论的技术,获取对 GridView s CommandField 中“编辑”按钮的编程引用, 在删除时添加 Client-Side 确认。 一旦有了参考,我们就可以隐藏或禁用该按钮。
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;
}
}
}
}
有了此事件处理程序,当以特定供应商的身份访问此页面时,停止使用的产品不可编辑,因为这些产品隐藏了“编辑”按钮。 例如,Chef Anton s Gumbo Mix 是新奥尔良卡琼喜悦供应商的停产产品。 访问此特定供应商的页面时,此产品的“编辑”按钮将隐藏在视线中(请参阅图 14)。 但是,使用“显示/编辑所有供应商”访问时,“编辑”按钮可用(请参阅图 15)。
图 14:对于 Supplier-Specific 用户,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 控件插入、更新和删除数据。 从下一教程开始,我们将关注添加分页和排序支持。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。