Limiting Data Modification Functionality Based on the User
Introduction
A number of web applications support user accounts and provide
different options, reports, and functionality based on the logged
on user. Back in the Limiting Data Modification Functionality
Based on the User tutorial, we examined how to dynamically
adjust the data modification capabilities of a DetailsView and
GridView based on the visiting user. Specifically, this earlier
tutorial allowed users to “log on” to the website
either as a user from a supplier or as an employee of our company
(Northwind Traders). If the “logged on” user was a
supplier, they were allowed to update information about their own
products and company. Users from Northwind Traders were able to
update product and supplier information from any company.
Note: Recall that in the Limiting Data Modification
Functionality Based on the User tutorial a user “logged
on” to the website by choosing their level of access from a
drop-down list (whether they could edit all suppliers or just a
particular one). ASP.NET 2.0’s membership system provides a
standardized, extensible platform for creating, managing, and
validating user accounts. However, an examination of the
membership system is beyond the scope of these tutorials. For
more on membership, refer to my Examining ASP.NET 2.0’s
Membership, Roles, and Profile article series.
In this tutorial, we’ll see how to limit the data
modification functionality based on the currently logged on user,
but do so using the DataList control. In particular, we’ll
create a page that lists employee information — their
name, title, and hire date — in an editable DataList.
Anonymous users cannot edit any employees (see Figure 1), but a
“logged on” user can edit her own employee record
plus any the records of any employee she manages (see Figure 2).
Any employee that does not have a manager can edit any
employee’s record.
Since the employee’s managerial position dictates whose
records they can edit, when testing this tutorial’s example
it’s important to be familiar with Northwind Trader’s
organizational hierarchy. Use the hierarchy shown in Figure 3 as
a reference when testing.
Let’s get started!
Step 1: “Logging On” to the Website
In a real-world ASP.NET 2.0 application, we’d take
advantage of the membership system and the security Web controls
to persist user account information, authenticate users,
associate user accounts with employees, and so on. To keep the
focus on working with data, we’ll forgo setting up
membership and instead simulate authentication by allowing the
visitor to pick which employee they want to be logged on as from
a DropDownList.
Start by opening the UserLevelAccess.aspx page in
the EditDeleteDataList folder and add a DropDownList
to the page, setting its ID property to
LoggedOnAs. From the DropDownList’s smart tag,
create a new ObjectDataSource named
LoggedOnAsDataSource and configure it to use the
EmployeesBLL class’s
GetEmployees() method (see Figure 4). Complete the
DropDownList’s data source configuration by having the
LastName data field displayed and
EmployeeID as the value for each list item, as shown
in Figure 5.
At this point, the LoggedOnAs DropDownList will
display the last name of each employee. In addition to being able
to render the appropriate data modification user interface for
“logged on” users, we also need to be able to handle
anonymous users. Therefore, let’s add a static item to this
DropDownList labeled “Anonymous Visitor” with a value
of “-1” (see Figure 6).
Figure 6: Add an “Anonymous Visitor” List Item to the LoggedOnAs DropDownList
Be sure to set the DropDownList’s
AppendDataBoundItems property to True.
By default, data bound items — such as the employees
bound from the ObjectDataSource — will overwrite any
statically-added items. By setting this property to
True, the data bound employee list items will be
appended to the static one (“Anonymous Visitor”).
Finally, set the DropDownList’s AutoPostBack
property to True.
After adding the DropDownList and ObjectDataSource and
configuring the DropDownList’s properties, your
page’s declarative markup should look similar to the
following:
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>
Take a moment to view our progress through a browser. The
drop-down list contains an “Anonymous Visitor” option
along with the last name of each of the employees in the
organization.
A visitor can “log on” to the site by selecting an
employee from the drop-down list, and they will be considered
signed in as that user.
Step 2: Creating the Editable DataList
With the faux login interface complete, our next step is to
build the editable DataList and limit its data modification
capabilities based on the “logged on” user. Before
concerning ourselves with limiting the functionality, let’s
first create a DataList in which every employee is editable
regardless of who is “logged on.” Once we’ve
completed the editable DataList, we will finish by limiting the
editing capabilities based on the user visiting the page.
As we’ve seen in previous tutorials, creating an
editable DataList involves:
- Adding the DataList and creating its read-only
interface
- Creating the editing interface
- Writing event handlers for the DataList’s
EditCommand, CancelCommand, and
UpdateCommand events
Let’s tackle each of these steps separately.
Building the Read-Only Interface
Start by dragging a DataList control from the Toolbox onto the
Designer. Set its ID property to
Employees and, from its smart tag, create a new
ObjectDataSource named EmployeesDataSource.
Configure the ObjectDataSource to return all of the employee
information using the EmployeesBLL class’s
GetEmployees() method, just like we did with the
LoggedOnAsDataSource (refer back to Figure 4, if
needed).
Once the data source has been configured, Visual Studio will
create the default ItemTemplate for the DataList,
displaying each data field’s name and value. Edit the
ItemTemplate so that it shows just the employee
name, title, and hire date. Also add a Button, LinkButton, or
ImageButton control for the Edit button. Recall that this
button’s CommandName property must be set to
“Edit”. After these additions, the
ItemTemplate should look as follows:
<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>
Note: The databinding expression assigned to the
HireDateLabel’s Text property
specifies a short date time format. The format specifier is
passed in as the second parameter to the Eval
method. For more information on specifying formatting information
via databinding syntax, refer back to the Using TemplateFields
in the DetailsView Control tutorial.
Figure 8 shows the page when viewed through a browser. Since
an EditCommand event handler hasn’t been
created yet, clicking the Edit button causes a postback, but does
not make the item editable.
Creating the Editing Interface
With the read-only interface complete, our next task is to
build the editing interface. For our editing interface
— spelled out in the DataList’s
EditItemTemplate — use a TextBox for each
of the four data fields. Next, add the Update and Cancel buttons.
Don’t forget to set the button CommandName
properties to “Update” and “Cancel”,
respectively. Additionally, add RequiredFieldValidators to ensure
that the first and last names are provided and use a
CompareValidator to check that the hire date value is a valid
date. Also include a ValidationSummary control on the page and
set the Cancel button’s CausesValidation
property to False.
Note: For more information on working with the validation
controls in ASP.NET 2.0, peruse the Validating Form Input
Controls section of the ASP.NET QuickStart
Tutorials.
Since we’ve built editing interfaces in previous
tutorials, let’s skip straight ahead to what the resulting
declarative markup should look like. For a walkthrough of
creating the DataList’s editing interface, consult the
Overview of Editing and Deleting Data in the DataList and
Customizing the DataList’s Editing Interface
tutorials.
<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>
Currently, the editing interface cannot be seen when visiting
the web page because the EditCommand event handler
that will mark a particular DataList item for editing has yet to
be created. We can, however, view the editing interface through
the Designer. From the DataList’s smart tag select the
“Edit Templates” option and choose
EditItemTemplate.
Writing the EditCommand,
CancelCommand, and UpdateCommand Event
Handlers
With the ItemTemplate and
EditItemTemplate finished, the final step is to
write the event handlers for the DataList’s
EditCommand, CancelCommand, and
UpdateCommand events. These are the events that fire
when the user clicks the Edit, Cancel, and Update buttons. The
EditCommand and CancelCommand events
simply update the EditItemIndex property and rebind
the data to the DataList — the EditCommand
event handler assigns the EditItemIndex to the index
of the item whose Edit button was clicked whereas the
CancelCommand event handler reverts it back to a
value of -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();
}
After adding these two event handlers, take a moment to view
the page in a browser. As shown in Figure 10, clicking the Edit
button marks an employee as editable and renders the item using
the EditItemTemplate. Clicking the Cancel button
returns the DataList to its pre-editing state without saving any
changes.
The UpdateCommand event handler is responsible
for propagating the user’s changes to the database. This is
accomplished by:
- Consulting the DataList’s
DataKeys
collection to grab the edited employee’s
EmployeeID value.
- Reading in the values from the editing interface. As we saw
in past tutorials, this can be accomplished by using the
FindControl("controlID") method to
programmatically reference the appropriate TextBox in the
EditItemTemplate.
- Calling the proper BLL method to update the employee
record.
Currently, our application architecture does not include any
means to update employee information. To add such functionality,
we’d need to add a method to the
EmployeesDataTable in the DAL as well as a
corresponding method to the EmployeesBLL class in
the BLL. The Creating a Data Access Layer and Creating
a Business Logic Layer tutorials explore these steps in
detail.
The thrust of this tutorial is to illustrate customizing the
data modification access rights based on the currently logged on
user. Therefore, I am going to leave this as an exercise for the
reader and will instead create a simple
UpdateCommand event handler that displays a
client-side messagebox explaining that the updating capabilities
are not yet implemented:
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();
}
Note: The Page.ClientScript property is
an instance of the ClientScriptManager
class, which contains a number of methods for
programmatically injecting client-side script into the rendered
output of an ASP.NET page. See Client Script in ASP.NET Web
Pages and Working with Client-Side Script for more
information.
Clicking the Update button displays the modal dialog box shown
in Figure 11.
Step 3: Limiting Editing Capabilities Based on the
“Logged On” User
Currently, any user — including anonymous visitors
— is able to edit any employee’s information. We
want to prohibit this and instead only allow authenticated users
to edit their records and the records of the employees they
manage (if any), with the one exception being if an employee has
no managers, then he can edit any employee.
To prevent a visitor from editing employees she is not
authorization to edit, we can hide those employees’ Edit
buttons. The DataList’s ItemDataBound event
fires once for each record bound to the DataList. We can create
an event handler that determines if the currently “logged
on” user has permission to edit the record and show or hide
the Edit button accordingly:
// 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;
}
}
Before the ItemDataBound event handler is a
page-level variable currentlyLoggedOnUser. This
variable holds information about the employee the visitor has
selected from the LoggedOnAs DropDownList and is
populated on demand in the ItemDataBound event
handler. In a real-world application, where authentication and
authorization are handled formally, such data would likely be
accessed when authenticating the user and persisted through
session variables, the authentication ticket, or some other
means.
Since the ItemDataBound event fires for all types
of items added to the DataList — headers, footers,
separators, items, editable items, and so on — the
event handler begins by ensuring that we’re dealing with an
item or alternating item. Next, the employee data that was just
bound to this DataListItem is referenced via the
DataItem property and cast to an
EmployeeRow object. Following that, information
about the currently “logged on” user is read into the
page-level currentlyLoggedOnUser variable, if
needed.
Once we have the information about the currently “logged
on” user and the employee whose record was just bound to
the DataList, we can apply our logic to determine whether the
employee record can be edited. If
currentlyLoggedOnUser is still Nothing
by this point, then we’re dealing with an anonymous user
who cannot edit any employee records. If it’s not
Nothing, we check to see if the authenticated user
does not report to anyone. If so, then they can edit any
employee. If the authenticated user does report to someone,
however, they are only able to edit the current employee record
if it is theirs or if they are the employee’s manager.
Finally, the Edit button is programmatically referenced via
the FindControl("controlID") method and its
Visible property is set.
When first visiting the page through a browser, the
“Anonymous Visitor” option is selected by default,
therefore hiding the Edit buttons in all employee records (see
Figure 12).
Selecting a different employee from the
LoggedOnAs DropDownList causes a postback (since we
set its AutoPostBack property to True),
but does not update the DataList. Since the DataList’s
ObjectDataSource doesn’t use any parameters based on the
DropDownList, its changes do not require the data to be rebound
to the DataList. Therefore, we need to manually instruct the
DataList to rebind its data whenever the DropDownList’s
selected index is changed.
Create an event handler for the DropDownList’s
SelectedIndexChanged event and add the following
code:
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();
}
Invoking the DataList’s DataBind() method
instructs the DataList to rebind its data. This causes the
ItemDataBound event to fire again as each item is
added, causing the Edit buttons to be shown or hidden based on
the newly selected user from the LoggedOnAs
DropDownList. Before calling the DataBind() method,
set the EditItemIndex property to -1 to
ensure that the DataList is rendered in its read-only state. If
this line of code is omitted and a user clicks the Edit button
for a particular DataList item and then selects a new user from
the LoggedOnAs DropDownList, the edited item will
remain in edit mode. We want to prohibit this since the user may
be changing who they are “logged on” as from someone
that can edit that employee record to one who cannot.
After completing the SelectedIndexChanged event
handler, take a minute to test the page in a browser. As Figure
13 shows, when logged on as Davolio, only Nancy Davolio’s
information is editable, but when logged on as Buchanan there are
four editable records — Steven Buchanan and his three
subordinates: Michael Suyama, Robert King, and Anne Dodsworth
(see Figure 14). Finally, when “logged on” as Fuller,
all employee records are editable, as shown in Figure
15.
Note: If the rules for editing employee information
— that employees can only edit their own records and
those of their subordinates, unless they report to no one, in
which case they can edit all records — are universal
across the application and not specific to just this web page, it
would be prudent to also add this check in the BLL. In the
EmployeesBLL class’s
UpdateEmployee method (which was left as an
exercise to the reader), you could raise an exception if the
currently logged on user didn’t have authorization to edit
the specific employee’s records. This exception could then
be gracefully handled in the ASP.NET web page. Refer back to the
Handling BLL- and DAL-Level Exceptions tutorial for more
information on throwing and handling exceptions among the layers
of the architecture.
Summary
Most sites that provide user accounts need to customize the
data modification interface based upon the currently logged on
user. Administrative users may be able to delete and edit any
record, whereas non-administrative users may be limited to only
updating or deleting records they created themselves. Whatever
the scenario may be, the DataList and Business Logic Layer
classes can be extended to add or deny certain functionality
based on the logged on user. In this tutorial we saw how to limit
what records were editable based upon the “logged on”
user.
This tutorial concludes our examination of inserting,
updating, and deleting data using the DataList. Starting with the
next tutorial, we’ll turn our attention to adding paging
and sorting support.
Happy Programming!
About the Author
Scott
Mitchell, author of seven ASP/ASP.NET books and founder of
4GuysFromRolla.com,
has been working with Microsoft Web technologies since 1998. Scott works
as an independent consultant, trainer, and writer. His latest book is
Sams
Teach Yourself ASP.NET 2.0 in 24 Hours. He can be reached at
mitchell@4GuysFromRolla.com.
or via his blog, which can be found at
http://ScottOnWriting.NET.
Special Thanks To…
This tutorial series was reviewed by many helpful reviewers. Lead reviewer for this tutorial was Dennis Patterson. Interested in reviewing my upcoming MSDN articles? If so, drop me a line at mitchell@4GuysFromRolla.com.
Next Tutorial