ASP.NET Tip/Trick: Use a Base Page Class for All Application Pages
Posted by DevExpert on 25th March 2009
All coders worth their salt know that duplicating code isn’t a best practice, and you should consolidate and leverage object inheritance where necessary. When done correctly, this promotes better maintainability and a better overall application.
I’ve been doing a lot of straight ASP.NET application programming lately, and no matter how many ASP.NET applications I write, I always use a certain number of common methods in my pages’ code-behind. For example, I get and set view state values, I retrieve values from the cache, or verify query strings have been included. Because I’m doing these same types of things in most of my pages, it makes sense to include a lot of the leg work in a base class that each of my pages can inherit. This blog post will provide you the default base class I always start with, and will explain and demonstrate a few of the methods.
At a high level, my base class is divided into the following four regions:
Query String Methods
Let’s take a look at the Query String Methods first. Many pages will have support for query strings, and most of the time these query string values will need to be validated. Consider a scenario where you are accessing data from a database, and you navigate to a page with a URL of http://webapp/page.aspx?id=10. You pull out the ID query string value and pass that in as a parameter and retrieve the appropriate results. But what if you modify the query string value and access a page with a URL of http://webapp/page.aspx?id=abc? Are you checking to make sure the value is numeric? Are you even checking to make sure an ID query string has been specified? Well, you should! A lot of this logic can be wrapped in a base class, and through the use of inheritance and overridden methods, the specific logic can be written.
I have three methods declared in my base class:
- RequiredQueryStrings: Returns a list of all required query string keys.
- CheckQueryStrings: Ensures all required query strings have been specified.
- CheckQueryStringValues: Ensures the specified query string values are actually valid.
These methods are declared as follows:
/// <summary> /// When overriden, returns a list of all required query string keys /// </summary> /// <returns></returns> protected virtual List<string> RequiredQueryStrings() { return null; } /// <summary> /// When overriden, checks the actual values of query strings /// </summary> /// <returns></returns> protected virtual bool CheckQueryStringValues() { return true; } /// <summary> /// Verifies the required query string are actually present /// </summary> /// <returns></returns> protected bool CheckQueryStrings() { List<string> values = RequiredQueryStrings(); if (values == null || values.Count == 0) { return true; } else { foreach (string value in values) { if (Request.QueryString[value] == null) { return false; } } } return true; }
In addition to these methods, my base page’s Init method checks these methods and depending on the results, allows the rest of the code to run or redirects you to an error page:
protected override void OnInit(EventArgs e) { base.OnInit(e); if (!CheckQueryStrings()) { // all required query strings have not been specified RedirectToPage("Error.aspx"); } if (!CheckQueryStringValues()) { // one or more query string values are invalid RedirectToPage("Error.aspx"); } }
To use this, I simply inherit my application pages from my PageBase class, and override the appropriate methods:
public partial class ViewWidget : PageBase { protected override List<string> RequiredQueryStrings() { List<string> values = new List<string>(); values.Add("widgetID"); return values; } protected override bool CheckQueryStringValues() { int widgetID = 0; int.TryParse(Request.QueryString["widgetID"].ToString(), out widgetID); return (widgetID > 0); } protected void Page_Load(object sender, EventArgs e) { } }
Notice all I’m doing is overriding the base class methods, and specifying the things that are required. It’s up to the base class to do the actual error handling, which in my case involves navigating to an error page. The important thing to note is that the error handling logic is declared in ONE place. Each individual page is only responsible for identifying the values that should be checked.
Now, when I access my ViewWidget.aspx page with a valid URL, such as http://webapp/ViewWidget.aspx?widgetID=10, I get the following page:
If I access it with an invalid URL, such as http://webapp/ViewWidget.aspx?widgetID=xyz, I get redirected to the error page:
Redirection Methods
Chances are your web application contains more than just one page, and you will have to navigate to other pages. Once in awhile ASP.NET will throw a ThreadAbortException when redirecting to another page, which doesn’t matter, and we don’t really need to do anything when that occurs. My redirection methods consist of two methods:
- RedirectToPage: Redirects to a page and ignores the exception that is sometimes thrown.
- RedirectToPageWithQueryStrings: Redirects to a page and includes all current query string keys and values. Since this method ultimately calls RedirectToPage, any exception is also ignored.
These methods are declared as follows:
/// <summary> /// Redirects the application to the specified page, and ignores the /// erroneous error that is sometimes thrown /// </summary> /// <param name="url"></param> protected void RedirectToPage(string url){ try{ Response.Redirect(url); } catch{ // catch the ThreadAbortException that is occasionally thrown by ASP.NET } }
/// <summary> /// Redirects to another page and carries over all current /// query string keys and values /// </summary> /// <param name="url"></param> protected void RedirectToPageWithQueryStrings(string url) { string queryStringList = string.Empty; if (!string.IsNullOrEmpty(url)) { if (Request.QueryString.Count > 0){ // rebuild the query string list for(int i = 0; i < Request.QueryString.Count;i++){ queryStringList += string.Format("{0}={1}&", Request.QueryString.GetKey(i), Request.QueryString.Get(i)); } // remove the erroneous ampersand queryStringList = queryStringList.TrimEnd(new char[] { '&' }); // append the '?' to the beginning if (!string.IsNullOrEmpty(queryStringList)) { url = "?" + queryStringList; } } RedirectToPage(url); } }
View State Methods
I utilize view state occasionally, and have included a couple methods to help manage this:
- GetViewStateValue: Retrieves a value from view state, and if it doesn’t exist returns the specified default value.
- SetViewStateValue: Sets a view state value.
- ClearViewStateValue: Clears a view state value.
These methods are declared as follows:
/// <summary> /// Retrieves a value from ViewState /// </summary> /// <param name="key"></param> /// <param name="defaultValue"></param> /// <returns></returns> protected object GetViewStateValue(string key, object defaultValue) { return ((ViewState[key] == null) ? defaultValue : ViewState[key]); } /// <summary> /// Sets a value in ViewState /// </summary> /// <param name="key"></param> /// <param name="val"></param> protected void SetViewStateValue(string key, object val) { ViewState[key] = val; } /// <summary> /// Clears a value from ViewState /// </summary> /// <param name="key"></param> protected void ClearViewStateValue(string key) { ViewState[key] = null; }
Cache Methods
I also leverage the application cache, and have included a few methods to help manage this as well:
- GetCachedItem: Retrieves a value from the application cache, and if it doesn’t exist returns null.
- SetCachedItem: Adds an item to the application cache.
- ClearCachedItem: Removes an item from the application cache.
These methods are declared as follows:
/// <summary> /// Returns an item from the application cache /// </summary> /// <param name="key"></param> /// <returns></returns> protected object GetCachedItem(string key) { object returnValue = null; try { returnValue = Cache.Get(key); } catch { } return returnValue; } /// <summary> /// Adds in item to the application cache /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="minutes"></param> protected void SetCachedItem(string key, object value, int minutes) { try { Cache.Insert(key, value, null, DateTime.Now.AddMinutes(minutes), TimeSpan.Zero); } catch { } } /// <summary> /// Clears an item from the application cache /// </summary> /// <param name="key"></param> protected void ClearCachedItem(string key) { try { Cache.Remove(key); } catch { } }
These are just a few of the methods that are good candidates for placement in a base class. You could just as easily as methods to manage session variables, or some other type of common functionality in your application. The point is to identify places where you can simplify and reuse code. This will make it much easier to develop, maintain, and enhance in the future.
I’ve included my base page in the ZIP file below. As always, my code is provide as-is and without warranty!
Download: PageBase.zip
Tags: .NET, ASP.NET
Posted in .NET, ASP.NET | 21 Comments »