With the user-facing pages done (except for the Login and Error page, these will be discussed later), we can now move on to the Admin screens. The Admin screens are only available for authorized users; we will see how security works later. From the Admin screens, authorized users can manage several aspects of Blogo.NET:
- Blog entries
- The error log
- The setup (configuration parameters)
Before we start to build the Admin pages, we must first implement the Admin master page. The Admin master page is a nested master page that inherits from the site master page. All it does is use the right column of the site to show a list of static links to various admin pages. This way, every admin page we will create that inherits from the admin master page, will have a consistent set of admin links in the right column. You can inspect the admin master file by opening the file Admin.Master from the View/Masters folder in the solution. For now, you can ignore the Login control on this master page. It will be discussed in the security section.
With the Admin master page defined, we can now start to create our first admin page. For each of the following entities that we want to manage from the admin panel…
- Blog Entries
…we will need to create two pages:
- A page to list the data. We will use a GridView for this, enabling us to select and delete data from a single overview.
- A page to edit/create data. We will use a single page to both edit existing data and create new data.
I will start first by explaining the simplest case: The Tags. Note that within Blogo.NET, all admin pages are located in the folder View/Pages/Admin.
We want administrators to be able to view tags, create new ones, delete existing ones and edit existing ones. The first page, AdminTags.aspx, will show the available tags using a GridView. The process to develop this page is as follow:
- Create new page in the Admin folder and inherit it from the Admin master page.
- Place an ObjectDataSource control on the page. This step is similar is similar as explained before. Do note the importance of the control settings:
Of specific importance are the pagination settings (EnablePaging, StartRow, PageSize, and Count), and that we have also defined the Delete data method.
Place a GridView control on the page and link it to our ObjectDataSource control. Next, we have to tune various settings to make the GridView look & behave the way we want. You can view all settings in the AdminTags.aspx file, below are the most important elements:
- Paging is enabled.
DataKeyNames is set to
id, this is needed to make the Delete functionally work.
- The GridView has three columns defined. The first column is the
tagname, which is a simple data-bound field. The second column is a hyperlink edit field. It uses the id field of the tag object to pass it to the edit/create form. The third column is the Delete link, which is a LinkButton. Note that this button has a client-side configuration script attached.
Once you have tuned the Grid to your needs, it will be easy to build the grids for the other admin pages. It is important to really understand which property does what though, so be sure to carefully review the settings in AdminTags.aspx.
With the GridView completed, we now have a page to list tags, delete individual tags and launch the edit form. We do not yet have a way to create a new tag though. We will implement this by simply placing a Button control above the grid. This button will simply launch the Tag create form:
The following code is launched upon clicking the button:
With the Grid in place, it is now time to implement the AdminTagEditNew.aspx form, which enables administrators to edit and create tags. Now that we have quite some experience data-binding standard ASP.NET controls to an ObjectDataSource, it is quite simple to develop such a form.
We will use the FormView control from ASP.NET. The FormView is suitable since it offers templates for both inserting and editing records. As always, we place an ObjectDataSource control. We configure it to the TagManager class in the Business layer. This time as SELECT method we configure it to use the
GetItem method of the object, since we only want to edit/create a single Tag object. We also configure the INSERT and UPDATE statement; both will point to the
Save method of the TagManager class.
With the ObjectDataSource control configured, we now add a FormView control and configure it to use the ObjectDataSource control we just defined. As part of the FormView control, we will define both the InsertItemTemplate and EditItemTemplate. Within these tags, we build the form. As always, this is a matter of using standard ASP.NET web controls (TextBox, Button, RequiredFieldValidator). Note that the buttons have their
CommandName property set to reflect the operation they provide, this is needed to make the data-bound operations work. I will not explain the process of creating these standard form elements, instead, please refer to the file AdminTagEditNew.aspx for the full source code.
Two remarks apply to this file:
- It seems that we have a lot of duplicate markup: both the insert and edit template from the FormView control have the same markup. A way to avoid this is to define the duplicated markup in a web user control and refer to that twice. I have chosen not to do this for Blogo.NET, since the reuse of such a control is limited to the page, and not the site. Despite the duplicate markup, it is reasonably convenient to apply markup updates twice, since it is on the same page.
- We have not done any coding to save or update the Tags, we simply relied upon standard data-binding fields and operations. Note however, that the FormView is a limited control. Although we got the job done without coding, more complex objects, such as blog entries, will require us to do custom coding to save/update objects. Luckily, this custom coding is extremely simple thanks to our well-defined Business layer.
We now have enough information to build the other admin grids and edit/create forms. There is one exception though; the form to create/edit blog entries deserves some extra attention.
Creating Blog entries
The form to create and edit blog entries is far more complex than the previously discussed tag form:
- It contains a lot more fields
- Fields need to be filled with default values
- The form contains an advanced rich text editor
- The Save/Update operation is custom-coded
What follows is an explanation of how this is implemented. It is recommended to open the file AdminBlogEditNew.aspx and follow along.
In concept, the blog form works the same as the tag form; it uses an ObjectDataSource control to data-bind with the Business layer, and a FormView to define the forms for inserting/editing records. However, since blog entry objects are complex in nature (in terms of relations to other objects), we need to do a few extra things.
First of all, the blog entry field “author” and “tags” are multi-value fields of related objects (authors and tags). Each requires a separate ObjectDataSource control in order for them to be displayed in a DropDownList control and CheckBoxList control, respectively.
Furthermore, we want some of these fields to be filled with sensible default values, depending on the mode (insert or edit), and the currently signed in user. This cannot be done from within the FormView, therefore the
Page_Load event takes care of this. Below is a fragment of this logic that shows how the default value for the author field is set in insert mode:
The following logic applies in edit mode:
In insert mode, the currently signed in user is used as the default value, whereas in edit mode, the original author of the existing blog entry is set as the default value. Note that the code accesses the field controls using the
FindControl method. Field controls that are placed inside FormView templates cannot be accessed any other way.
The next step is to register the main TinyMCE script in the blog form. We can do this by placing the following markup below the
form start tag:
From now on, we have access on the blog form to the TinyMCE API. The next part is a bit tricky; we have to make a complex init call to TinyMCE. In this init call we supply various parameters to control how the rich text editor should behave:
It goes too far to explain every TinyMCE parameter in this article, please refer to the TinyMCE documentation for more detail. Nevertheless, here is a brief overview:
- The init script should be placed just below the ScriptManager tag.
- Mode: By setting this to
exact, we can specify to only load the rich-text editor for specific controls.
- Elements: This states for which control the editor should be loaded. We want to load it for the BlogBody control. However, we cannot refer to the ID
BlogBody, since ASP.NET will in fact convert this ID during runtime into
ctl00_ctl00_ContentPlaceHolderPrimary_ContentPlaceHolderPage_FormViewBlog_BlogBody. Hideous as it looks, this is the only way to get this to work.
- The next set of parameters essential control what rich text editor buttons will be available in the editor.
relative_urls: This is set to
true to make sure that images we insert in the editor have a relative path.
document_base_url: This is the base of the relative URL, it is calculated by the property
GetAppRoot in the code-behind file of the form.
External_image_list_url: This is the URL that TinyMCE uses to fill its pick-list of images that users can insert the editor. We have set it to an aspx file that generates the list of available images.
The BlogBody control to which we apply the rich text editor looks like this:
As you can see, the
TextMode is set to
MultiLine, which renders the TextBox as a TextArea.
There is one more thing to do to finish the implementation of the rich text editor. We need to call TinyMCE’s
triggerSave method before saving the rich text content to our database. This is done in the
OnClientClick event of our
While we’re at it, let us discuss how the complex fields inside the blog form are saved into the database. As stated before, we cannot do this without coding; the FormView control is too limited to support the save operation of such a complex object. Therefore, in the
SaveButton_Click event in the code-behind, we need to do some custom coding to build up a BlogEntry Business Object, and then save it to the database:
This listing may be a bit overwhelming, let’s break it up into small pieces:
- First, a BlogEntry Business Object is created or retrieved, depending on the mode (insert or edit).
- Next, the BlogEntry object’s properties are set. For simple properties, such as
title, this is a matter of finding the control inside the FormView and copying its value.
- For complex properties (properties that are in fact other objects), we need to translate the form field value to an object reference. For example, the
author property is an object. The code above translates the selected author’s ID, and looks up the object by using the AuthorManager class from the Business layer. Another example is the
tags property of the BlogEntry object, this is in fact a collection of objects, Tag objects to be specific. Therefore, we need to loop though the selected tags in the form, create a Tag object for each, and add it to the tags collection. Note that this does not create new tags in the database; it only associates existing tags to this BlogEntry Business Object. Technically speaking, it does create
blog_tag mappings in the database, but we do not need to even know this, since we abstracted this implementation detail away in the Business layer.
- After building up the complex BlogEntry Business object, we simply save it using the BlogEntryManager class from the Business layer. Next, we redirect the user back to the admin overview of blog entries.
This concludes the explanation of the blog form. It is by far the most complex admin form. Based on this explanation you should be able to understand the code of the admin's other forms. Nevertheless, here are a few hints to understand the other ones:
- AdminAuthorEditNew.aspx. This form is used to edit/create users. It has some custom code in the Save operation that will be explained in the security section of this article.
- AdminFileNew.aspx. This form allows administrators to upload files (so that they can use images in the blog form’s rich text editor). Unlike the other admin form, this one has a single mode: insert. It is not possible to edit existing files, instead you have to delete them and then re-upload. The
Save operation of this form has custom code to transform the uploaded file to a format that can be used by the FileManager class from the Business layer to insert the file content into the database.
- AdminCommentEdit.aspx. This file is used to edit comments made by the user. It is not possible to insert new comments from here, as this requires the context of a blog entry. The
Save operation has some simple custom code to update the edited comment into the database.
- AdminLogEntry.aspx. This form is used to display error messages only, needless to say it is not possible to create or edit error messages.
- AdminSetup.aspx. This form is used to edit Blogo.NET configuration settings. It is different from the other admin forms in that it does not retrieve data from the database or write to the database. Instead, it uses a Blogo.NET Utils class (BlogoSettings.cs) to store and read/write settings from and to the web.config file. It does not make sense to store these settings in the database, as there is always only one instance of it. The downfall of this method is that the ASP.NET runtime account requires write access to your application folders. However, since we only read/write settings from/to the web config via the BlogoSettings class, all you would need to do store the settings in the database instead, is modify this class. A flexible design, so to speak.
This concludes the section about the creation of the admin pages. Relatively seen it was the most complex part to implement, yet thanks to our master page architecture and solid Business layer, it is very much doable.
Developing Blogo.NET – Security
Up until now we have not discussed security at all. Luckily Blogo.NET’s security model is simple:
- Only administrators can use any of the admin functions.
- Administrators are defined in Blogo.NET’s
- Administrators need to log in and identify themselves before they can use any of the admin functions. Once authenticated, they can use all functions.
It is reasonably simple to implement this model. Let us first return to the way authors (administrators) are managed inside Blogo.NET. Author objects are very simple: they only have a username and password. However, it is not wise to store passwords as plain text in the database. This is why we store the hashed value of the password. By applying the same hash algorithm to the user’s password upon login, we can compare the result with the hashed value in the database to see if the user has supplied a correct password.
Note that this still leaves a security vulnerability open: if someone manages to access the database and see all hashed passwords, they can run a brute force dictionary attack to reverse-engineer the passwords. By supplying a unique “salt” value to the hash, this problem is solved. The logic to generate salted hashes is located in the Code/Utils/Hash.cs file. The
Save method of the Data Access object for Authors makes use of this logic store users. Below is the relevant code fragment from Code/Data/Access/Author.db.cs:
The AdminAuthors.aspx and AdminAuthorEditNew.aspx pages enable administrators to manage the users.
ASP.NET offers different ways for users to authenticate them. We just learned that we have a custom implementation for managing users. We also know that Blogo.NET is an internet application. This excludes the option to use Windows authentication with an Active Directory provider. Instead, we will use FormsAuthentication, in combination with a custom login form and membership provider to implement authentication.
First, we need to enable FormsAuthentication in the web.config file:
Noteworthy settings in this section of the web.config are:
LoginURL: The address of our Login form (discussed next).
Name: The name of the login cookie to use.
defaultURL: The address to go to upon a successful login.
While we’re in the web.config, we might as well setup our custom MembershipProvider as well:
This will tell the application to use our custom MembershipProvider, called “BlogoMembershipProvider”, discussed later.
Finally, we need to set up some authorization rules in the web.config. For Blogo.NET, they are simple: All pages can be accessed without authentication, except for the pages in the “View/Pages/Admin” folder:
With the web.config properly set up, it is now time to have a closer look at our custom MembershipProvider. It is located at Code/Utils/BlogoMembershipProvider.cs. The purpose of this custom MembershipProvider is to authenticate users to Blogo.NET’s user store. We do this by inheriting from ASP.NET’s MembershipProvider base class. There are many methods we can override from the base class, but we will only override one: ValidateUser. Below is the relevant code fragment, as taken from the BlogoMembershipProvder.cs class:
As the code comments explain, the process is as follow:
- Use the AuthorManager class from the Business layer to count the number of defined users. If there are none, Blogo.NET is not correctly set-up, therefore allow the default username and password “admin”. If at least one user is defined, “admin” is not accepted, and Blogo.NET is operated securely
- Based on the username supplied, look up the Author object from the Business layer. If it is found, compare the supplied password with the hashed value stored in the database. If it matches, the authentication is successful.
- If no such user is found, or if the hash does not match, authentication fails.
With the web.config modifications and the custom MembershipProvider in place, developing the login form is easy; it is located at View/Pages/Login.aspx and contains only a few lines of code:
- The login form inherits directly from the site master page.
- On the page, a Login control is placed. It is configured to use our custom MembershipProvider. We also configured the error text in case authentication fails.
- We have included a label control in the bottom. This label is used to display instructions in case there is no user defined. The label is filled with text in the
Page_Load event of the form.
With very little work, we now have implemented a powerful, custom security model by reusing much of ASP.NET standard facilities:
- When users access any visitor page, no login form appears.
- When users access any admin page, they are first directed to the login page (if they already authenticated this is detected from the cookie, and the login page is skipped).
- At the login form, the credentials are checked by the custom Membership provider.
- Upon failure, the authentication error text is displayed.
- Upon success, the user is authenticated and redirected to the page originally requested.
We will finish the implementation of the security model by enriching the user experience a little. We will display the login status of the user in the admin sidebar, along with an option to log out of the application.
As you might recall, the admin sidebar is defined in the admin master page (View/Masters/Admin.Master). The following code displays the name of the currently logged in user, along with an option to log out:
Note how the logout link points to the page Logout.aspx. This page is very simple, all it does is clear the current authentication in the
Page_Load event of the form, and then redirect the user back to the Login form:
This completed the implementation of the security model.
Developing Blogo.NET – Error handling
We are almost done. The last thing to consider is error handling. The error handling pattern applied to Blogo.NET is fairly standard:
- Errors that we expect upfront are catched and handled wherever possible. An example of this is that we check if a
tagname already exists before saving a new one. If we would not perform this check, the database would return an error.
- Unexpected (unhandled errors) are catched by an application-level error handler. This error handler will log the full details of the error, but only display a friendly error page to the user. If an error occurs during the error logging, it is catched and no further action is taken, otherwise we will end up in an endless loop.
Let’s see how this is implemented. First, we will need a way to store error messages in the database. This step is in fact already done, since the log table is part of our database model, and we have provided a Log Business object and LogManager Business Object Manager to store messages in the database.
The next thing to do is to write code in the Global.asax file of the application, which is located in the application root. This file contains several application-level events. We are interested in the
Application_Error event. This event will be triggered each time an unhandled exception occurs, so this is where we want to place our logic:
Note that no error is logged yet. All that happens is that the user is redirected to the error page. Let’s have a look at the error page, Error.aspx. This is a very simple page, it contains static markup to tell the user that an error occurred. The page directly inherits from the site-level master page, Site.Master. The actual logging of the error to the database takes place in the
Page_Load event of the page, where the LogManager class from the Business layer is used to log the error details:
As you can see,
Server.GetLastError() is used to retrieve the last unhandled exception that occurred in the database.
This concludes the explanation of how I developed Blogo.NET. My conclusion is that with a proper n-tier architecture, standardized and new ASP.NET controls and a bit of experience, you can quickly produce elegant, good-looking applications that are easy to maintain and extend in the future. You may even have fun in the process!
I hope you enjoyed this article, please use the comment and rating form on my website to provide feedback. Thanks for taking the time to read this.
"I am a software engineer and architect from the Netherlands. I've started out with Lotus Notes development in 1999 and have since then developed and certified myself in PHP, J2EE and .NET. In my free time, I blog and have a small open source web site for my projects. Furthermore, I enjoy m...
This author has published 3 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.