基于用户限制用户修改功能43
简介
许多 Web 应用程序支持用户帐户并基于登录的用户提供不同的选项、报表和功能。回到 基于用户来限制数据修改功能 教程,我们介绍了如何根据正在访问的用户动态调整数据修改功能。特别是,前面的这个教程允许用户作为供应商用户或公司 (Northwind Traders) 雇员“登录”网站。如果“登录的”用户是供应商,允许他们更新有关他们自己的产品和公司的信息。而来自Northwind Traders 的用户可以更新任何公司的产品和供应商信息。
注意: 记住在 基于用户来限制数据修改功能 教程中,用户通过从下拉列表中选择访问级别(他们可以编辑所有的供应商还只是特定的供应商)而“登录” 网站。 ASP.NET 2.0 的成员资格系统可以提供标准化的、可扩展的平台供创建、管理和验证用户帐号使用。但是,详细地介绍成员资格系统超出了本教程的范围。有关成员资格的详细信息,请参阅 介绍 ASP.NET 2.0 的成员资格、角色和配置文件 文章系列。
本教程中,我们将了解怎样基于当前登录的用户来限制数据修改功能,不过是使用DataList 控件。具体来说,我们将创建一个页面在可编辑的 DataList 中列出员工信息—— 他们的名字、头衔和雇用日期。匿名用户不能编辑任何员工(参见图 1 ),但是“登录的”用户可以编辑自己的员工记录和她管理的任何员工的记录(参见图 2 )。没有经理的员工可以编辑任何员工的记录。
图1:匿名用户不能编辑任何员工的记录
图2 :“ 登录” 的用户可以编辑自己的记录和她管理的员工的记录
由于员工的管理职位限制他们可以编辑的记录,当测试此教程的示例时,熟悉Northwind Trader 的组织层次结构就很重要。测试时使用如图 3 所示的层次结构作为参考。
图3 :Northwind Trader 的组织层次结构
让我们开始吧 !
步骤1 :“登录” 到网站
在实际的ASP.NET 2.0 应用程序中,我们将利用成员资格系统和安全 Web 控件来保留用户帐户信息、验证用户以及将用户帐户和员工相关联等。为了将重点放在处理数据上,我们没有设置成员资格,而是允许用户从 DropDownList 中选择他想以哪个员工的身份登录来模拟身份验证。
首先打开EditDeleteDataList 文件夹中的UserLevelAccess.aspx 页并向页面添加一个 DropDownList ,将其ID 属性设置为 LoggedOnAs 。从DropDownList 的智能标记,创建一个名称为 LoggedOnAsDataSourcemart 的新的 ObjectDataSource 并将其配置为使用 EmployeesBLL 类的 GetEmployees() 方法(参见图4 )。如图 5 所示,通过使 LastName 数据字段显示出来以及将 EmployeeID 做为每个列表项的值,完成DropDownList 的数据源配置。
图4 :将 ObjectDataSource 控件配置为使用 EmployeesBLL 类的 GetEmployees() 方法
图5 :使每个列表项显示 LastName 并使用 EmployeeID 作为值
现在LoggedOnAs DropDownList 将显示每名员工的姓。除了能为“登录”的用户呈现相应的数据修改用户界面之外,我们还需要能够处理匿名用户。因此,我们此DropDownList 添加一个标记为 “Anonymous Visitor” 、值为-1 的静态项(如图 6 所示)。
图6 :向 LoggedOnAs DropDownList 添加一个 “Anonymous Visitor” 列表项
确保将DropDownList 的 AppendDataBoundItems 属性设置为True 。默认情况下,数据绑定项—— 如从 ObjectDataSource 绑定的雇员—— 将覆盖任何静态添加项。通过将此属性设置为 True ,数据绑定员工列表项将追加到静态项(“Anonymous Visitor”)。最后,将DropDownList 的 AutoPostBack 属性设置为True 。
添加 DropDownList 和 ObjectDataSource 并配置完 DropDownList 的属性之后,页面的声明式标价应该与以下类似 :
You are logged on as:
<asp:DropDownList ID="LoggedOnAs" AutoPostBack="True"
AppendDataBoundItems="True" DataSourceID="LoggedOnAsDataSource"
DataTextField="LastName" DataValueField="EmployeeID" runat="server">
<asp:ListItem Value="-1">Anonymous Visitor</asp:ListItem>
</asp:DropDownList>
<asp:ObjectDataSource ID="LoggedOnAsDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetEmployees" TypeName="EmployeesBLL">
</asp:ObjectDataSource>
花点时间在浏览器中查看一下进度。下拉列表中包含一个“Anonymous Visitor” 选项以及组织中的每名员工的姓。
图7:下拉列表包含一个 “Anonymous Visitor” 选项和一个针对每名员工的列表项
访问者可以通过从下拉列表中选择一名员工的方式 “ 登录 ” 到站点,他们将被当作该用户登录。
步骤2 :创建可编辑的 DataList
完成模拟的登录界面之后,下一步是构建可编辑的 DataList 并根据“登录”的用户限制它的数据修改功能。在我们关注限制功能之前,首先创建一个 DataList ,其中的每个员工都是可编辑的,而不管哪个人“登录”。完成可编辑的 DataList 之后,我们将以根据访问页面的用户来限制编辑功能来完成这一部分内容。
正如我们在前面的教程中所看到的,创建可编辑的 DataList 包括:
- 添加 DataList 和创建它的只读界面
- 创建编辑界面
- 为 DataList 的 EditCommand 、CancelCommand 和 UpdateCommand 事件编写 Event Handler
让我们分别处理每一步。
构建只读界面
首先从工具箱将一个 DataList 控件拖放到设计器上。将其 ID 属性设置为Employees ,并从其智能标记创建一个名称为 EmployeesDataSource 的新的 ObjectDataSource 。使用 EmployeesBLL 类的 GetEmployees() 方法将 ObjectDataSource 配置为返回所有员工的信息,就像我们使用LoggedOnAsDataSource 时所做的一样(如果需要,请参阅图 4 )。
配置完数据源之后,Visual Studio 将为 DataList 创建默认的 ItemTemplate ,用来显示每个数据字段的名称和值。编辑 ItemTemplate 以便使它只显示员工的名称、头衔和雇用日期。再为Edit 按钮添加一个 LinkButton 或 ImageButton 控件。记住必须将此按钮的 CommandName 属性设置为 “Edit” 。进行这些添加之后,ItemTemplate 应该如下所示:
<ItemTemplate>
<h4>
<asp:Label ID="FirstNameLabel" runat="server"
Text='<%# Eval("FirstName") %>' />
<asp:Label ID="LastNameLabel" runat="server"
Text='<%# Eval("LastName") %>' />
</h4>
Title:
<asp:Label ID="TitleLabel" runat="server"
Text='<%# Eval("Title") %>' />
<br />
Hire Date:
<asp:Label ID="HireDateLabel" runat="server"
Text='<%# Eval("HireDate", "{0:d}") %>' />
<br />
<br /><br />
<br /><br />
<asp:Button runat="server" ID="EditButton"
CommandName="Edit" Text="Edit" />
<br /><br />
</ItemTemplate>
注意:赋给HireDateLabel 的 Text 的数据绑定表达式指定了短的日期时间格式。格式说明符做为第二个参数传递给 Eval 方法。有关通过数据绑定语法指定格式的详细信息,请参阅 在 DetailsView 控件中使用 TemplateField 教程。
图 8 显示的是通过浏览器查看时的页面。由于还未创建EditCommand Event Handler ,单击 Edit 按钮可以引起回传,但是不会使项成为可编辑的。
图8 :显示每名员工的名称、头衔和雇用日期
创建编辑界面
完成只读界面之后,下一项任务是构建编辑界面。对于编辑界面—— 在 DataList 的 EditItemTemplate 中说明—— 四个数据字段的每一个都使用一个 TextBox 。下一步,添加 Update 和Cancel 按钮。不要忘记将按钮的CommandName 属性分别设置为 “Update” 和“Cancel” 。此外,添加 RequiredFieldValidators 以确保提供姓和名并且使用CompareValidator 来检查雇用日期的值是合法的日期。另外页面上还包括ValidationSummary 控件并将 Cancel 按钮的CausesValidation 属性设置为 False 。
注意:有关在 ASP.NET 2.0 中使用校验控件的详细信息,请仔细阅读 ASP.NET 快速入门教程 的验证表单输入控件 一节。
由于我们已经在前面的教程中构建了编辑界面,让我们直接向前跳过,看看生成的声明式标记应该是什么样。有关创建DataList 的编辑界面的演示 , 请参阅 在 DataList 中进行数据编辑与删除操作概述 和 定制 DataList 的编辑界面 教程。
<EditItemTemplate>
First Name:
<asp:TextBox runat="server" ID="FirstName" Columns="10" MaxLength="10"
Text='<%# Eval("FirstName") %>' />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
ControlToValidate="FirstName"
ErrorMessage="You must provide the employee's first name."
runat="server">*</asp:RequiredFieldValidator>
<br />
Last Name:
<asp:TextBox runat="server" ID="LastName" Columns="20" MaxLength="20"
Text='<%# Eval("LastName") %>' />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2"
ControlToValidate="LastName"
ErrorMessage="You must provide the employee's last name."
runat="server">*</asp:RequiredFieldValidator>
<br />
Title:
<asp:TextBox runat="server" ID="Title" Text='<%# Eval("Title") %>'
Columns="30" MaxLength="30" />
<br />
Hire Date:
<asp:TextBox runat="server" ID="HireDate" Columns="15"
Text='<%# Eval("HireDate", "{0:d}") %>' />
<asp:CompareValidator ID="CompareValidator1" ControlToValidate="HireDate"
ErrorMessage="The hire date must be a valid date value."
Operator="DataTypeCheck" Type="Date"
runat="server">*</asp:CompareValidator>
<br />
<br />
<asp:Button ID="UpdateButton" runat="server"
Text="Update" CommandName="Update" />
<asp:Button ID="CancelButton" Text="Cancel" CausesValidation="False"
CommandName="Cancel" runat="server" />
</EditItemTemplate>
现在,访问网页时看不到编辑界面,这是因为还未创建用来标记特定的 DataList 编辑项的 EditCommand Event Handler 。但是,我们可以通过设计器来查看编辑界面。从 DataList’s 智能标记中选择 “Edit Templates” 选项并且选择 EditItemTemplate 。
图9:通过设计器预览编辑界面
编写 EditCommand 、CancelCommand 和 UpdateCommand Event Handler
完成ItemTemplate 和 EditItemTemplate 之后,最后一步是为DataList 的 EditCommand 、CancelCommand 和UpdateCommand 事件编写 Event Handler 。他们都是当用户单击 Edit 、Cancel 和 Update 按钮时触发的事件。EditCommand 和 CancelCommand 事件只更新 EditItemIndex 属性并将数据重新绑定到DataList——EditCommand Event Handler 将 EditItemIndex 赋给其 Edit 按钮被单击的项的索引,而 CancelCommand 事件处理程序又将其还原到值 -1 :
protected void Employees_EditCommand(object source, DataListCommandEventArgs e)
{
// Set the EditItemIndex to the index of the item whose Edit button was clicked
Employees.EditItemIndex = e.Item.ItemIndex;
Employees.DataBind();
}
protected void Employees_CancelCommand(object source, DataListCommandEventArgs e)
{
// Return the DataList to its pre-editing state
Employees.EditItemIndex = -1;
Employees.DataBind();
}
添加这两个 Event Handler 之后,花些时间在浏览器中查看页面。如图 10 所示,单击 Edit 按钮可以将员工标记为可编辑的并使用 EditItemTemplate 呈现项目。单击 Cancel 按钮可以使 DataList 返回到其编辑前的状态,而不保存任何更改。
图10 :Edit 和 Cancel 按钮现在都起作用
UpdateCommand Event Handler 负责将用户的更改提交到数据库。这是通过以下步骤完成的 :
- 查阅 DataList 的 DataKeys 集合以获取被编辑的员工的 EmployeeID 值。
- 从编辑界面读取值。正如我们在前面的教程中所看到的,这可以通过使用 FindControl("controlID") 方法通过编程引用 EditItemTemplate 中相应的 TextBox 来完成。
- 调用适当的 BLL 方法来更新员工的记录。
现在我们的应用程序架构没有包括任何更新雇员信息的方法。要添加该功能,我们需要向DAL 中的EmployeesDataTable 添加一个方法以及向 BLL 中 EmployeesBLL 类添加一个相应的方法。 创建数据访问层 和 创建业务逻辑层 教程详细地介绍了这些步骤。
本教程的要点是说明根据当前登录的用户来定制数据访问权限。因此,我打算将此做为练习留给读者,这里创建一个简单的 UpdateCommand Event Handler,显示一个解释还未实现更新功能的客户端消息框:
protected void Employees_UpdateCommand(object source, DataListCommandEventArgs e)
{
// Display a client-side messagebox explaining that the updating capabilities
// are not yet implemented
Page.ClientScript.RegisterStartupScript(this.GetType(),
"NotYetImplemented",
@"alert('Update capabilities are not yet implemented and are left" +
" as an exercise to the reader - that\'s you!');", true);
// Return the DataList to its pre-editing state
Employees.EditItemIndex = -1;
Employees.DataBind();
}
注意:Page.ClientScript 属性是 ClientScriptManager 类 的实例,它包含许多通过编程将客户端脚本注入到呈现的ASP.NET 页的输出的方法。有关详细信息,请参阅 ASP.NET 网页中的客户端脚本 和 使用客户端脚本 。
单击 Update 按钮可以显示如图 11 所示的模态对话框。
图11 :添加更新支持是留给读者的练习
步骤3 :基于“登录的”用户限制编辑功能
现在,任何用户—— 包括匿名访问者—— 都可以编辑任何员工的信息。我们想对此进行禁止,只允许经过身份验证的用户编辑他们的记录和他们管理的员工的记录(如果有的话),其中的一个例外是如果员工没有经理,则他可以编辑任何员工的记录。
为防止访问者编辑没有授权他们进行编辑的员工,我们可以隐藏这些员工的 Edit 按钮。一旦每个记录都绑定到 DataList 就会触发 DataList 的 ItemDataBound 事件。我们可以创建一个 Event Handler,用它来确定当前“登录的”用户是否具有编辑记录的权限并相应地显示或隐藏 Edit 按钮:
// A page-level variable holding information about the currently "logged on" user
Northwind.EmployeesRow currentlyLoggedOnUser = null;
protected void Employees_ItemDataBound(object sender, DataListItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.AlternatingItem ||
e.Item.ItemType == ListItemType.Item)
{
// Determine the Manager of the Employee record being bound
// to this DataListItem
Northwind.EmployeesRow employee =
(Northwind.EmployeesRow)((System.Data.DataRowView)e.Item.DataItem).Row;
// Read in the information for the currently "logged on" user, if needed
if (currentlyLoggedOnUser == null &&
Convert.ToInt32(LoggedOnAs.SelectedValue) > 0)
{
EmployeesBLL employeeAPI = new EmployeesBLL();
currentlyLoggedOnUser =
employeeAPI.GetEmployeeByEmployeeID(
Convert.ToInt32(LoggedOnAs.SelectedValue))[0];
}
// See if this user has access to edit the employee
bool canEditEmployee = false;
if (currentlyLoggedOnUser != null)
{
// We've got an authenticated user... see if they have no manager...
if (currentlyLoggedOnUser.IsReportsToNull())
canEditEmployee = true;
else
{
// ok, this person has a manager...
// see if they are editing themselves
if (currentlyLoggedOnUser.EmployeeID == employee.EmployeeID)
canEditEmployee = true;
// see if this person manages the employee
else if (!employee.IsReportsToNull() &&
employee.ReportsTo == currentlyLoggedOnUser.EmployeeID)
canEditEmployee = true;
}
}
// Referrence the Edit button and set its Visible property accordingly
Button editButton = (Button)e.Item.FindControl("EditButton");
editButton.Visible = canEditEmployee;
}
}
在ItemDataBound Event Handler 的前面是一个页面级变量currentlyLoggedOnUser 。此变量保存有关访问者从LoggedOnAs DropDownList 中选择的员工的信息并在ItemDataBound Event Handler 中按需要进行赋值。在实际的应用程序中,会正式处理身份验证和授权,当对用户进行身份验证时可能访问这些数据并通过会话变量、验证票证或其他方式保存。
由于对添加到DataList 的任何类型的项目—— 页眉、脚注、分隔符、项目、可编辑项目等—— 都会触发 ItemDataBound 事件,因此 Event Handler 首先确保我们正在处理项目或替代项目。接下来,通过DataItem 属性引用绑定到此DataListItem 的员工数据并强制转换为 EmployeeRow 对象类型。在此之后,如果需要,可以将有关当前“登录的”用户的信息读入到页面级的 currentlyLoggedOnUser 变量中。
一旦我们拥有有关当前“登录的”用户的信息和记录刚被绑定到 DataList 的员工的信息,我们就可以应用我们的逻辑来确定是否可以编辑员工的记录。如果现在currentlyLoggedOnUser 仍然是 Nothing ,则我们正处理的用户是不能编辑任何员工记录的匿名用户。如果不是 Nothing ,则查看是否经过身份验证的用户不向任何人汇报。如果是这样,则他们可以编辑任何员工的记录。但是,如果经过身份验证的用户向某些人汇报,则只有当前的记录是他们的或他们是当前记录的员工的经理时,才可以编辑当前的员工记录。
最后,通过FindControl("controlID") 方法通过编程访问 Edit 按钮并设置其 Visible 属性。
当第一次通过浏览器访问页面时,默认情况下选中 “Anonymous Visitor” 选项,因此在所有的员工记录中隐藏 Edit 按钮(参见图 12 )。
图12 :匿名访问者不能编辑任何员工的记录
从LoggedOnAs DropDownList 中选择一个不同的员工可以引起回传(因为我们将它的 AutoPostBack 属性设置为 True ),但是不会更新 DataList 。由于 DataList 的ObjectDataSource 没有使用任何基于 DropDownList 的参数,它的更改不要求数据重新绑定到 DataList 。因此,每当 DropDownList 的选定的索引更改时,我们需要手动指示 DataList 重新绑定它的数据。
为DropDownList 的 SelectedIndexChanged 事件创建一个Event Handler 并添加以下代码:
protected void LoggedOnAs_SelectedIndexChanged(object sender, EventArgs e)
{
// Make sure editing is disabled and rebind the data to the DataList
Employees.EditItemIndex = -1;
Employees.DataBind();
}
调用DataList 的DataBind() 方法可以指示DataList 绑定它的数据。这导致每次添加项目时都再次触发ItemDataBound 事件,根据从LoggedOnAs DropDownList 中新选择的用户引起 Edit 按钮显示或隐藏。在调用 DataBind() 方法之前,将 EditItemIndex 属性设置为 -1 以确保 DataList 以只读状态呈现。如果省略这行代码,并且用户单击特定 DataList 项的 Edit 按钮,然后从 LoggedOnAs DropDownList 中选择一个新的用户,所编辑的项将仍处于编辑模式。由于用户可以更改他们登录的帐号,那么有可能从一个可以编辑该员工的记录的帐号转换到另一个不能编辑该员工记录的帐号,我们要禁止这种情况。
完成SelectedIndexChanged Event handler 之后,花些时间在浏览器中测试页面。如图 13 所示,当以 Davolio 身份登录时,只有 Nancy Davolio 的信息是可编辑的,但是当以 Buchanan 身份登录时,有四个可编辑记录——Steven Buchanan 和他的三个下属:Michael Suyama 、Robert King 和 Anne Dodsworth (参见图14 )。最后,当以 Fuller 身份“登录”时,所有的员工记录都是可编辑的,如图 15 所示。
图13:Davolio 只可以编辑自己的信息
图14:Buchanan 可以编辑四个员工记录—— 他自己的和他下属的
图15:Fuller 没有经理,因此可以编辑所有的员工记录
注意:如果编辑员工信息的规则(员工只能编辑自己的记录和他们下属的记录,除非他们不向任何人汇报,这种情况下他们可以编辑所有的记录) 在整个应用程序中是通用的,而不是只特定于此网页,在BLL 中另外加入此检查是有远见的。在 EmployeesBLL 类的 UpdateEmployee 方法(留给读者做为练习)中,如果当前登录的用户没有编辑特定员工记录的权限,可能引发异常。随后可以在 ASP.NET 网页中正常地处理此异常。有关在架构的各层中抛出和处理异常的详细信息,请参阅 在 ASP.NET 页面中处理 BLL 与 DAL 级别的异常 教程。
小结
提供用户帐户的大多数站点需要基于当前登录的用户自定义数据修改界面。管理员用户可以删除和编辑任何记录,而非管理员用户可能限制为只能更新和删除他们自己创建的记录。无论是何种方案,都可以基于登录的用户来扩展 DataList 和 业务逻辑层 类以添加或拒绝某个功能。本教程中,我们了解了怎样基于“登录的”用户来限制哪些记录是可编辑的。
本教程是介绍有关使用 DataList 进行插入、更新和删除的最后一篇。从下一篇教程开始,将把我们的注意力转到添加分页和排序支持。
快乐编程!