Scott Mitchell 的ASP.NET 2.0数据教程之四十三::基于用户对修改数据进行限制
导言
很多支持帐号(即需要登陆)的web程序根据登陆的用户提供不同的选项,报表和其它功能。回到基于用户对修改数据进行限制 里,我们学习了如何根据当前用户来动态调整在DetailsView和GridView里修改数据的能力。它允许用户可以作为supplier或employee登陆到站点。如果登陆的用户为supplier,他将可以修改他提供的product信息和company信息。而employee可以修改任何公司的product和supplier信息。
注意:在基于用户对修改数据进行限制里,用户通过从下拉列表里选择访问权限级别来登陆到站点(无论他们可以编辑所有或一个supplier)。ASP.NET 2.0的membership 系统为创建管理和验证用户帐号提供了一个标准的可扩展的平台。然而详细的讨论membership 超出了本章的范围,更多的membership 的信息参考Examining ASP.NET 2.0’s Membership, Roles, and Profile
本章我们将学习如何使用DataList来根据当前登陆用户来显示数据修改功能。我们将创建一个列出employee 信息的页—name, title, 和hire date —在一个可编辑的DataList里。匿名用户将不能编辑任何employees 信息,见图1。而登陆用户可以编辑他自己的employee 记录以及他管理的所有employee 信息。见图2。
图 1: 匿名用户不能编辑任何Employee的 Record
图 2: 登陆用户可以编辑他自己的employee 记录以及他管理的所有employee 信息
由于employee的管理职位决定了他可以编辑那些记录,因此当测试本章的例子时候,熟悉Northwind 的组织等级是非常重要的。你可以使用图3的等级图作为参考。
现在我们开始!
第一步: 登陆到站点
在ASP.NET 2.0程序里,我们使用membership 和安全控件来保存用户帐号信息,鉴别用户,将用户帐号和employees关联。为了在数据处理上集中精力,我们将放弃创建membership,而是允许用户在DropDownList挑选他想作为哪个employee 来登陆。
打开EditDeleteDataList文件夹里的UserLevelAccess.aspx页,拖一个DropDownList 进来,将ID设为LoggedOnAs。从它的智能标签里创建一个名为LoggedOnAsDataSouce的ObjectDataSouce。使用EmployeesBLL类的GetEmployees()方法配置它。见图4。将LastName字段作为显示,EmployeeID作为value。见图5。
图 5: 配置DropDownList的Text和Value
现在LoggedOnAs DropDownList 显示每个employee的last name。为了对用户显示正确的数据修改界面,我们还需要处理匿名用户。因此,我们在DropDownList 里添加一个静态的项,Text为“Anonymous Visitor”,value 为“-1”,见图6。
图 6: 向DropDownList里添加“Anonymous Visitor”
注意要将DropDownList的AppendDataBoundItems属性设为True.默认的,绑定的数据项—比如从ObjectDataSouce绑定的employees — 会覆盖静态添加的项。将这个属性设为True后,绑定的employee 会附加在静态数据的后面(“Anonymous Visitor”).最后将DropDownList的AutoPostBack属性设为True.完成这些后,页面的声明代码看起来应该如下:
ASP.NET
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”选项和每个employees 的last name。
用户可以从下拉列表里选择一个employee 然后登陆到站点,这时系统会认为他是作为他选择的用户登陆进来的。
第二步: 创建可编辑的DataList
完成了“假的”登陆界面后,下一步我们来创建可编辑的DataList ,和根据“登陆”的用户来限制他的数据修改能力。我们首先创建一个全部可编辑的DataList (象前面教程里做的那样)。完成这个后,我们再来限制用户的修改能力。如我们在前面教程里看到的,创建可编辑的DataList 需要:
添加DataList 并创建只读界面
创建编辑界面
为DataList的EditCommand, CancelCommand, 和UpdateCommand events写事件处理
我们来分别实现这些步骤。
创建只读界面
首先拖一个DataList 进来。将ID设为Employees,然后通过智能标签创建一个名为EmployeesDataSouce的ObjectDataSouce。用EmployeesBLL类的GetEmployees()方法配置它,和我们配置LoggedOnAsDataSouce一样(可以参考图4。)
配置完数据源后,Visual Studio会自动为DataList创建ItemTemplate,显示每个字段的name和value。编辑ItemTemplate让它只显示employee 的name,title和hire date。然后添加一个Button, LinkButton, 或ImageButton 作为编辑按钮。完成这些后,ItemTemplate看起来应该如下:
ASP.NET
<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事件处理还没有被创建,现在点编辑按钮会引起postback但不会让item变为可编辑的。
图 8: 显示Employee的 Name, Title, 和Hire Date
创建编辑界面
完成只读界面后,下面需要创建编辑界面。我们的编辑界面— 在DataList的EditItemTemplate里描叙的— 使用TextBox 来显示每个字段。然后添加更新和取消按钮。不要忘了将button的CommandName属性分别设为Update” 和“Cancel”。另外还要添加RequiredFieldValidators 来确保用户输入了first name和last name,添加CompareValidator 来检查hire date 的值是合法的日期值。在页里添加一个ValidationSummary 控件,并将Cancel button的CausesValidation属性设为False.
注意:更多的在ASP.NET 2.0里使用验证控件的信息请参考Validating Form Input Controls 。
由于前面的教程里我们已经创建过编辑界面了,我们现在直接看看声明代码是什么样子。创建DataList编辑界面的详细信息请参考综叙:在DataList里编辑和删除数据 和自定义DataList编辑界面 。
ASP.NET
<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的智能标签里选择“编辑模板”然后选择EditItemTemplate。
完成 EditCommand, CancelCommand, 和UpdateCommand Event Handlers
完成ItemTemplate和EditItemTemplate后,最后一步是为EditCommand,CancelCommand和UpdateCommand事件写事件处理。这些事件在用户点编辑,取消和更新按钮时被激发。EditCommand和CancelCommand事件要做的只是更新了EditItemIndex属性并将数据重新绑定到DataList—EditCommand事件处理将被点了编辑按钮的项的索引赋值给EditItemIndex,而CancelCommand事件处理将EditItemIndex的值还原为 -1:
C#
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();
}
增加了这两个事件处理后,浏览该页。见图10,点编辑按钮会让employee 变为可编辑,点取消按钮会让DataList返回到编辑前的状态,并且任何修改都不会被保存。
UpdateCommand事件处理负责将用户的修改提交到数据库。这个可以通过下面的步骤完成:
访问 DataList的DataKeys 集合来获取被编辑的to employee的 EmployeeID .
从编辑界面获取更新后的值.如我们在前面的教程里看到的,这个可以通过 FindControl("controlID") 方法来编程引用 EditItemTemplate.里的 TextBox 来完成。
调用合适的BLL 方法来更新.
现在我们的程序还没有包含更新employee 信息的方法。我们需要在DAL的EmployeesDataTable和BLL的EmployeesBLL里分别添加一个方法。在创建一个数据访问层 和创建一个业务逻辑层 里有详细的介绍。
本章的焦点内容是如何根据当前的登陆用户来自定义数据修改权限。因此上面的添加更新方法将作为练习留给读者自己完成。在这里我只简单的创建一个UpdateCommand事件处理来显示一个客户端消息框,告诉用户更新的功能还没有被实现。
C#
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 class的一个实例,它包含了一些可以通过编程来在ASP.NET page里生成客户端脚本的方法。更多的信息参考Client Script in ASP.NET Web Pages 和Working with Client-Side Script 。
点编辑按钮会出现一个提示框,见图11。
第三步: 根据登陆用户来限制编辑的权限
现在,任何用户—包括匿名用户—都可以编辑任何的employee信息。我们需要阻止这种情况,而只让授权用户来编辑他们自己的和他们管理的employees 的(如果有的话)记录。唯一例外情况是如果用户没有上级,他就可以编辑所有的employees 记录。
为了阻止用户编辑他没有被授权编辑的employees ,我们可以隐藏这些employees 的编辑按钮。DataList的ItemDataBound事件在每次数据绑定到DataList时激发。我们可以创建一个事件处理来判断当前登陆的用户是否有权编辑记录,然后决定是否显示相对应的编辑按钮。
C#
// 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事件处理前是一个页面级的变量currentlyLoggedOnUser。这个变量用来保存用户在LoggedOnAs下拉列表里选择的employee 信息,并在ItemDataBound里使用。真实情况下,这样的数据一般都通过session变量来保存。
由于ItemDataBound事件在所有项加到DataList里时都会激发— headers, footers, separators, items, editable items,等— 因此在事件处理的最开始要先确保我们处理的是一个item 或alternating item。然后通过DataItem属性来引用刚刚绑定到DataListItem的employee 数据,并赋给一个EmployeeRow对象。跟着当前登陆用户的信息被读到currentlyLoggedOnUser变量里。
一旦我们有了当前登陆用户和刚绑定到DataList的employee 的信息,我们可以运用我们的逻辑出来来决定employee 数据是否可以被编辑。如果currentlyLoggedOnUser还是空的,我们就知道当前用户是匿名的,他不能编辑任何记录。如果不是空的,我们就检查他是否不从属于任何人,如果是这样,他就可以编辑任何employee记录。如果他从属于某个人,他就只能在当前记录是自己的或者当前记录从属于他的情况下编辑当前的employee 。最后通过FindControl("controlID")方法来获的编辑按钮的引用,并设置它的Visible属性。
第一次浏览该页时,匿名用户是被默认选中的,因此会隐藏所有employee 记录的编辑按钮。见图12。
从LoggedOnAs下拉列表里选择不同的employee 会引起postback(由于我们将它的AutoPostBack属性设为True),但是DataList并没有被更新。由于DataList的ObjectDataSouce并没有使用基于DropDownList的参数,在postback时不会请求数据绑定到DataList。因此我们需要手动来让DataList重新绑定数据。
为DropDownList的SelectedIndexChanged事件创建事件处理,并添加以下代码:
C#
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事件在添加每个item时又被激发,从而使编辑按钮根据新选择的用户判断来显示或者隐藏。在调用DataBind()方法前,将EditItemIndex属性设为-1来保证DataList为只读状态。如果没有这行代码,用户点了一个项的编辑按钮,然后选择一个新用户后,刚才可编辑的项现在仍然将保持为可编辑的状态。由于用户可能从一个可编辑某项的帐号换到另一个不可编辑此项的帐号,因此我们要避免这样的情况。
完成了SelectIndexChanged事件处理后,浏览该页。见图13。当作为Davolio登陆时,只有Nancy Davolio的信息是可编辑的,如果作为Buchanan 登陆的话有四条记录可编辑— Steven Buchanan 和他的三个下属: Michael Suyama, Robert King, 和Anne Dodsworth ,见图14。如果作为Fuller登陆的话,所有的记录都是可编辑的,见图15。
图 13: Davolio 只能编辑她自己的Employee 信息
图 14: Buchanan 可以编辑四条记录— 他自己的和他下属的
图 15: Fuller 没有上级, 因此他可以编辑所有Employee 记录
注意:如果编辑employee 信息的规则—employee只能编辑他自己的和他下属的信息,在他没有上级的情况下他可以编辑所有的记录—不仅仅在这页使用,而是整个程序通用的,那么可以在BLL里添加这些逻辑。在EmployeesBLL类的UpdateEmployee方法里(留给读者完成的)你可以在当前登陆用户编辑他没有权限编辑的信息的情况下抛出异常。更多抛出和处理异常的信息请参考处理BLL和DAL的异常 。
总结
很多支持帐号(即需要登陆)的站点根据登陆的用户提供不同的数据修改界面。管理员可能可以删除或编辑任何记录,而非管理员用户只能编辑和删除他们自己创建的记录。不管情况是怎样,DataList和BLL都可以扩展到根据当前登陆用户添加和拒绝某个功能。本章我们学习了如何根据登陆用户来限制那些记录可以被编辑。本章也结束了我们对使用DataList来插入更新和删除数据的学习。下一章开始我们将转向排序和分页。
祝编程愉快!