When writing ASP.NET application, we usually do a lot of design on the back end isolating different application layers, focusing on domain related design etc… However, as soon as we get close to the UI layer programming, the code becomes a bit messy. My explanation of this phenomenon is that the closer we are to the UI, the less the code is reusable or at least, meant to be reusable. Although, that may be true (that the code is not aimed to be reused), UI code must be at least clean and maintainable.
UI code should use OOP principles such like Inheritance and encapsulation as the code in any other layer. Moreover, some of the SOLID principles can, and should, be applied when designing our user controls and web pages. As web forms are still widely used, and I believe it will remain the case for a long time, in this article, I am exposing some design and programming rules that I use in my ASP.NET projects. Hopefully, following these rules will help having a cleaner code which will ease your UI code maintenance.
1. Use a base class for the UI classes
It is a good habit to start your project by creating at least a BasePage and BaseUserControl classes and make all your forms and user controls inherit from them. The reason is that you will certainly need to put some code in common to all or part of your web forms or user controls.
For instance, all pages of our site will have a title and eventually some meta-tags. Adding a PageID property and a method that retrieves the title and the meta-tags from a CMS based on the PageID will be achievable pretty easily using inheritance and the maintenance of the code will obviously easiest.
Another common scenario is the need to show/hide elements on a web form depending on a certain state. Adding a virtual method that is called by the OnPreRender event of the base class will make the mechanism of hiding and showing elements of the base centralized in one method overwritten by all the web forms. This is an implementation of the Open Close principle using template method design pattern.
using System;using System.Collections.Generic;
public class BasePage : System.Web.UI.Page
protected override void OnPreRender(EventArgs e)
public string PageID
/// Overwrite this method to enable or disable controls
/// depending on the state of the page.
protected virtual void DoEnableDisableControls()
private void SetTitleFromCMS()
//Queries the CMS for the page title
this.Title = "Whatever we got from the CMS";
private void SetMetaTagsFromCMS()
//Queries the CMS for the MetaTags
//add the meta tags
A page that derives from the base page will just need to set the PageID property and it will have a title and meta-tags (note the PageID property assigned in the page directive).
<%@ Page Language="C#" PageID="DefaultPage" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ExtRefWebApp._Default" %>
Using such a base class will let you inject common code at any time in the development process that will benefit all inheriting pages in a consistent way.
2. Encapsulate session variables
Following are the reasons why I never use session variables directly in the code but I rather encapsulate them into classes:
- Session variables are widely used in web applications. They are very helpful but we must not forget that a session variable is nothing else than a global variable from an OOP perspective.
- When debugging or maintaining a Web application, we often come across a piece of code that makes usage of a session variable and we need to check where this session variable has been set. It is a bit difficult to find out.
- Session is a property bag and it can hold any type. If we change the type stored for a certain key, we will need to check all the code and change the castings (we do this using the text search in the code).
- A session variable either exists or not (null value). There is no way to have a default value for instance and thus, whenever we use a session variable, we must check its value (check whether it is a null value) before using it.
The best way to avoid these issues is to have a set of classes that hold session variables and expose them through properties. Using the ‘Find all references’ of a property setter will find all the objects that set the session variable. On the other hand, if we change the type held in the session variable, the solution will eventually not compile and therefore, it will be very easy to make all the changes or to estimate the amount of work necessary to make the change. It makes the code more type safe.
Session variables should be encapsulated in different classes if we have too many and these classes which, preferably, should belong to the same namespace and eventually be in the same folder to ease their maintenance.
There is certainly much more to say about Session variables usage than what is here. For instance, using sessions in other logical layers of the application than the UI is common; a business layer class that makes usage of the session. Needless to say that, in this case, we violate the encapsulation principle and the application layers isolation since the business layer depends on the HTTP context (we no longer can reuse it in a non HTTP application type).
3. Do not create a deep UI controls hierarchies
User controls are a very good mean to re-use UI code. However, when we start to embed user controls into other user controls without a limit, we end up with an endless tree of nested controls. This makes the maintenance of the application a real hell.
Usually, it is a good habit to limit the tree to 2 or 3 levels maximum. The page contains controls that may contain controls. But the inner controls must contain only .NET Framework basic controls (or a server control). And even at the same level, we must limit the number of user controls included in the same user control. For instance, creating one big control that contains many inner controls is not always a good idea. Bear in mind that a page can contain controls as well: I have seen very often developers who do not have any ASP.NET controls in the page but embed everything in a ‘super’ user control that in turn, contains child controls. Why should we add this level of indirection and complexity? A page is a perfect container for controls.
4. Not everything should be embedded in a separate user control
If we do not pay enough attention, we may quickly have a huge number of controls in a web application. To avoid this, I usually follow the rules below in order to determine whether or not UI functionality must be in a separate control:
- Reusability: if we know that this control is going to be reused somewhere else, we must embed it in a user control,
- Complex rules: Sometimes it is better to separate certain parts of a user interface because we want to encapsulate some of its complexity into a separate class and file. This is nothing else the single responsibility principle applied to UI components.
For instance, a large form that contains multiple sections, that may communicate eventually, will be better implemented if we isolate the sections in different controls and make them communicate using events as explained in the next point.
5. Use events to communicate between user controls and their containers
Using a property in a user control to communicate a state with its container control is a common pattern we see in web development. In most cases, developers will set a session variable when something happens in the user control and the container (being it the page or another user control) will check the value of the session variable in order to determine whether or not something happened. This is usually done in the ‘OnLoad’ event. You will notice that pretty quickly, we end up with a pretty messy code in this event handler.
First, we must ensure that the code that sets the session variable runs before the code that checks its value. Also, we must ensure that the value is reset otherwise, next time we hit the same page, we may consider the event even though, it did not happen yet.
One elegant way to notify a status change from one control to another is to expose an event to which the container will subscribe. There are many advantages to this approach:
- Clean code that reacts to a specific event from the inner control,
- The same mechanism can be used to notify as many controls as we want,
- We are sure that when the event happens we will be notified whereas, using session variables adds an uncertainty since we do not know which code is executed first: the code that sets the session variable or the code that checks it.
- We do not have to care about the session variable and reset it after the event is over since we no longer use it.
- We do not use a session variable just to hold a flag (save memory and respect basic OOP principles).
6. Avoid embedding code in the ASPX files
The best place to add code to a web form is the code behind file. Embedded code in the ASPX files makes the code messy and very difficult to maintain as in the old ASP days.
7 . Use declarative code as much as possible
If you need to set a user control, or any server control, property do it in the ASPX file instead of the code behind. There case where we do not have the choice (conditional values for instance), but in general this makes the HTML code clearer and increases the maintainability since we know much about the control just by looking at one file (no need to check the code behind).
8. Be careful when using static variables
Static members are shared and leave as long as the ASP.NET process runs. This means whenever you use static members, you are consuming memory that won’t be released unless done explicitly.
One example I have seen in a past project is storing user specific information in a static dictionary (it could be any type of collection by the way). When load tested, the application memory usage grew up to 12 GB for a couple of hundreds of users. As the dictionary was static, its size was just adding up as new users came in and at the end, we had an ‘Out of memory exception’.
In this particular scenario, we should have put in place a timer and a method that cleans up from time to time the dictionary in order to avoid the situation described above. Though, we removed the dictionary and got the information from the database each time the code needed it (no caching).
9. Consider the performance from the client side perspective
In terms of performance, we often focus only on the code that executes on the server and neglect the bandwidth usage and number of downloads that a page contains. While CPUs and memory is cheaper, the bandwidth on the other hand is more expansive and, unless we are implementing an intranet in a controlled environment, we do not have any control on the available bandwidth for our web site users.
There are many tools and ways to make the page performance better on the client side (a good starting point would be this article: http://www.codeproject.com/KB/aspnet/PeformanceAspnet.aspx).
On the server side code, we should take care of not using too much ViewState. In fact, storing an object tree in the ViewState may look helpful and more efficient than storing it in the session variables since the object will not be held indefinitely in memory. Performance will be worse actually since your object tree is serialized and sent as part of the response to the client browser. And when the client submits a form, the serialized object is sent back again to the server.
There are too many topics on the client side performance to be all covered in this article and the two links cited above will give you a very good starting point if you never considered this particular point.
10. Use caching whenever possible
User controls can be cached using the OutputCache directive. User controls that display content grabbed from a CMS for instance, should consider using this directive.
We can also cache the content from the CMS in static members or using EnterpriseLibrary caching objects. If we do so, we must ensure that we do cache the closer form of the content to the UI. For instance if you grab XML from a CMS and transform it with an XSLT to generate the HTML to be displayed, you better cache the resulting HTML rather than the XML. Caching the HTML will avoid retransforming the XML the next time we hit the page.