Rolling Your Own Website Administration Tool - Part 1
Introduction
Forms-based authentication combined with ASP.NET 2.0's Membership and
Roles systems makes creating and managing user accounts incredibly easy. I continue to be amazed at how
the login-related Web controls encapsulate the array
of tasks that I had always had to code by hand in classic ASP. For more on the Membership and Roles systems, be sure to
read the Examining ASP.NET 2.0's Membership, Roles, and
Profiles article series.
To help administer users, roles, and authorization settings, ASP.NET 2.0 includes the Web
Site Administration Tool (WSAT). WSAT is available from the Visual Studio 2005 Website menu via the ASP.NET Configuration
menu option. Launching the WSAT from Visual Studio, however, allows only local websites to be administered.
Such restrictions are limiting when hosting a website remotely with a web hosting company. (Granted, the WSAT's files
are available in the %WINDOWS%\Microsoft.NET\Framework\v2.0.50727\ASP.NETWebAdminFiles
folder and can be
deployed from there.)
Rather than move the existing WSAT tool to my remote host, I decided to build my own WSAT-like tool from the ground up. My version duplicates all features inside the Security section of the WSAT and adds a useful "Access Rules Summary" view of the website security as applied to any given user or role. The complete code can be downloaded from the end of this article and added to your site within a matter of minutes. This article provides an overview of my custom WSAT implementation and explores the user list and add and edit user pages in detail. Part 2 explores the role management and access rules sections in detail. Read on to learn more!
Using My Custom Website Administration Tool
My complete customized WSAT application can be downloaded at the end of this article. The download includes a fully-functioning
shell of a dummy corporate intranet. Its global navigation menu contains one link for each department - IT, marketing,
sales, and so on - with each department's web pages existing as a separate, physical folder in the website.
This demo uses the SqlMembership
provider, storing user information in the ASPNETDB.MDF
database
in the application's App_Data
folder. This database is a Microsoft SQL Server 2005 Express Edition database.
Follow these steps to get up and running with this sample application:
- Copy the contents to the hard drive of your development machine, which must have both Visual Studio 2005 and SQL Server Express installed. The free Visual Web Developer version will work just fine.
- Open Visual Studio.
- Click File --> Open Web Site from the main menu, browse to the folder where you extracted the contents downloaded at the end of this article, and open up the website.
- Click the green arrow to start debugging. Visual Studio should start its built-in Web server and the login page should be displayed.
- Log in with user name "Dan Clem", password "dan" (omitting the quotation marks). Dan Clem is the only administrator in the system. You can log in as a non-administrator using any of the following user names: "Edward Eel", "Franklin Forester", "Gordy Gordon", "Harold Houdini", or "Ike Iverson". The password for each of these users is their first name, all lower case. Note that non-administrators are unable to visit the custom WSAT page.
- Click the Admin link from the global navigation menu to begin reviewing the application. The other links on the global navigation menu are simple placeholders for future development work on our dummy corporate intranet. They are used to demonstrate the Access Rule Management and Access Rule Summary pages.
- Copy the
admin
folder into your website's folder structure so that it exists directly underneath the root folder of your ASP.NET application. (In my application, I've mapped the ASP.NET application to the root website folder, but it should still work if your ASP.NET application is a subfolder inside the website.) - Copy the
Alphalinks
User Control (alphalinks.ascx
) into a suitable spot inside your website, then modify the@Register
directive on theusers.aspx
page to match its new location. (For a more detailed discussion on configuring and working with User Controls, see An Extensive Examination of User Controls.) - Copy the images found in the
i
folder to a suitable spot in your images folder, then modify the image links on theaccess_rules.aspx
andaccess_rule_summary.aspx
pages accordingly. - Make certain to register the following namespaces in the
system.web
section of your web application's rootweb.config
file. They are needed for theDataTable
andDirectoryInfo
classes used by some of the web pages in the custom WSAT application:<pages>
<namespaces>
<add namespace="System.Data" />
<add namespace="System.IO"/>
</namespaces>
</pages> - You'll need to modify the pages to fit your master page and navigational menu techniques. (I use a hybrid ASP.NET 2.0/classic ASP approach, where the global navigation menu appears in a single master page that is shared throughout the site, while the secondary navigation menus appear as include files specific to each subfolder.)
- Note that I've split my
admin
folder into two subfolders:access
andactivity
. The custom WSAT application is fully contained inside theaccess
folder. I haven't yet developed theactivity
folder, but I intend to add web pages that provide a log of what users are viewing what pages. - Secure the
admin
folder to the appropriate Roles or Users using the steps outlined in the "Securing Our Custom WSAT" section of this article.
Managing User Accounts
The Security section of the official
ASP.NET 2.0 WSAT provides a set of pages for managing a website's
users, roles, and access rules. In creating my custom WSAT application,
I have rebuilt all of the core WSAT pages inside this section.
Additionally, I added a useful "Access Rules Summary" page and other
features that, in my opinion, improve the overall page flow and
usability. I started out by building a set of user list pages that I
felt would cover the needs of my typical deployments. I broke this out
into separate pages for working with:
- Users by Name
- Users by Role
- Active Users
- Online Users
- Locked Out Users
In the Users by Name view the GridView is populated with the website's users via the following two lines of code:
Users.DataSource = Membership.GetAllUsers();
|
The Membership
class
is part of the .NET Framework and includes methods for retrieving information about users in the system. The
GetAllUsers()
method,
for example, returns all users in the site.
I wanted to add an alphabetic filter to match the user interface found in the official WSAT. I decided to build this as an
ASP.NET User Control so that I could reuse it later. Because I'm just learning how to build ASP.NET controls, this task took a
bit longer than I planned, but I learned a couple things along the way, so allow me to share. I built two versions of
this User Control, which I've called Alphalinks
. Coming from classic ASP, I still have a strong bias toward the simplicity of
QueryString-based navigation, so that's how I built the first version of this control. The control simply outputted all
letters of the alphabet as regular links, adding a QueryString parameter in the form &letter=X
. This worked
great until I tried to use it alongside an ASP.NET DropDownList
control whose AutoPostBack
property
was set to True. I soon realized that combining the ASP.NET postback and traditional QueryString-based navigation models
opens a can of worms.
With yet another lesson learned and yet another minor heartbreak behind me, I said goodbye to the QueryString-based
navigation model and built a new version as a proper postback-based ASP.NET User Control. I won't go into the details here,
but I quickly realized an advantage that comes with postback-based controls: we can access the control values as properties
rather than using the Request.QueryString
paradigm of the classic ASP world. In particular, I added a
Letter
property to the Alphalinks
User Control that returned the selected letter. With this property
added, the control could be used programmatically, like so:
Users.DataSource = Membership.FindUsersByName(Alphalinks.Letter + "%");
|
A Look at the Users by Role View
The Users by Role view uses a GridView to list the users that belong to a specified role.
In place of the Alphalinks
User Control, this page has a DropDownList that lists the roles in the system.
This DropDownList is populated with the roles using the Roles
class's
GetAllRoles()
method like so:
UserRoles.DataSource = Roles.GetAllRoles();
|
The filtering logic for this page is a trifle more complicated because there are no methods in the Membership or Roles sytems
that return the details for all users in a particular role. While the Roles
class does have a
GetUsersInRole(roleName)
method, it returns just the usernames of the users in the specified role and not the users' details like their email
address, active status, last logon date, and so forth. I overcame the lack of such a method by writing code that returns
all user details and then all users in a particular role. I then loop through the usernames in the specified role
and add the corresponding user details to a filtered collection.
// Get all of the users
|
The final three user-related pages (Active Users, Online Users, and Locked Out Users) were all straightforward. The
filtered lists were created by getting all user details via Membership.GetAllUsers()
, iterating through the
returned MembershipUsers
collection, and checking the appropriate Boolean property of each
MembershipUser
object in the collection to determine whether to add it to a filtered collection. This filtered collection was then
bound to the GridView on the web page. Note that "Active" corresponds to the
IsApproved
property of the
MembershipUser
class, "Online" to IsOnline
,
and "Locked Out" to IsLockedOut
.
Alternative Approaches to Filtering Users |
---|
The logic used to filter users by role and by active, online, and locked out status all work the same, in general. They get
all user details by calling Membership.GetAllUsers() and then iterate through the resultant collection.
This approach, however, does not scale as the user accounts grow into the thousands and beyond. If you expect more than a
few hundred user accounts, consider ditching the built-in Membership API and querying your user store using more efficient
techniques.
For example, if you're using the |
Creating New User Accounts
The Membership system makes it pretty easy to add new users via the Membership
class's
CreateUser
method. My custom WSAT application contains an Add User page, which contains a CheckBoxList for Role selection
followed by the expected form fields for the pertinent new user attributes.
When designing the Membership system, Microsoft decided to capture only essential user information, things like the user's name, their password, a comment about the user, their last login date, and so on. If we need to capture additional user-specific fields for our application - such as gender, address, date of birth, and so on - we can either use the Profile system or create our own infrastructure for storing this information. For more on ASP.NET 2.0's Profile system, see Profiles in ASP.NET 2.0; check out Erich Peterson's article, Customizing the CreateUserWizard Control for an example of storing additional user data in a custom table.
The following screen shot shows the Add User page in action:
The code to add a new user is simple: one line of code adds the user to the system; a second line then adds the comments
(because none of the CreateUser
method overloads includes a comments parameter). After the user has been added,
a foreach
loop associates the new user with each of the selected roles.
protected void AddUser()
|
Editing Existing User Accounts
The Edit User page is based on the design of the Add User page and can be accessed from the various user list pages. I
built this page using a CheckBoxList for the Roles and a DetailsView for the primary user info. Because the
CheckBoxList is separate from the DetailsView control, I had to enable/disable the checkboxes manually to make them stay
in sync with the built-in edit/view mode of the DetailsView:
private void Page_PreRender()
|
I also had to write some code to add or remove the user from roles, as necessary. This code is called from the
OnItemUpdating
event of the DetailsView.
private void UpdateUserRoles()
|
Conclusion
This article provided an oview of my custom WSAT application (available for download at the end of this article), examining
the user list pages and add and edit user pages in detail. The pages explored in this article make up the user management
portion of the tool. There are also role management pages and web pages for specifying authorization rules. These topics
are covered in Part 2.
Until then... Happy Programming!
By Dan Clem