Skip navigation

Data Application Tricks

Performance, Reliability, and Accuracy

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 3.5

Data-driven applications are becoming more common these days, and the need for data at a user s fingertips is more prevalent. These types of systems have different requirements, relying more on performance, reliability, and data accuracy than look and feel (and sometimes functionality). Although each application has different levels of criticality, the goal is the same: provide data at a user s fingertips.

Sometimes systems rely on certain sets of data more than others. For instance, a customer portal starts out with a customer, then drills down to related data from that central focal point. A sales engine relies more on the sales data, then drills down into demographic information.

This article provides tips and tricks for more of the data-centric applications, where data is a key component of the day-to-day business. The accompanying sample application is a customer portal, driven by the need to access customer data, along with the related products they purchase and the orders they make (see end of article for download details).

Page Architecture

ASP.NET uses a class that represents an ASP.NET page; when creating a new page, it has both markup and a code-behind class, which is linked together to create the output the user sees. The code-behind class is a public class that inherits from System.Web.UI.Page, the base class for ASP.NET pages. This class contains the plumbing to process requests for ASP.NET pages.

To reference the page, the System.Web.UI.Control base class (from which even the Page class inherits) has a reference to the Page in which the control is defined. This reference points to the code-behind class of the page currently executing.

Remember that ASPX code-behind classes are required to inherit from System.Web.UI.Page indirectly, not directly. What this means is that another level of inheritance can be inserted between the Page class and the ASPX code-behind class. This is a very useful technique, because it allows the reuse of portions of code throughout the application, similar to the Singleton pattern. Let s look at an example of this; Figure 1 shows a custom page class developed for a Customer Portal sample I m developing. This custom page class defines useful events, properties, and methods that are beneficial to the application.

 

public class CustomerPortalPageBase : PageBase

{

 private CustomerPortalDAL.CustomersRow _selectedCustomer = null;

 private Guid _selectedCustomerKey = Guid.Empty;

 public event EventHandler SelectedCustomerChanged;

 public bool HasSelectedCustomer

 {

   get { return (this.SelectedCustomerKey != Guid.Empty); }

 }

 public Guid SelectedCustomerKey

 {

   get

   {

     if (_selectedCustomerKey == Guid.Empty)

     {

object value = this.Services.Caching.Get(

      this.Services.Caching.GetSafeKey(

      "SelectedCustomerKey"));

       if (value != null)

         _selectedCustomerKey = new

      Guid(value.ToString());

     }

     return _selectedCustomerKey;

   }

   set

   {

     if (_selectedCustomerKey != value)

     {

       _selectedCustomerKey = value;

       this.Services.Caching.Add(

      this.Services.Caching.GetSafeKey(

"SelectedCustomerKey"), _selectedCustomerKey);

       this.OnSelectedCustomerChanged(EventArgs.Empty);

     }

   }

 }

 public CustomerPortalDAL.CustomersRow GetSelectedCustomer()

 {

   if (_selectedCustomer == null &&

      this.SelectedCustomerKey != Guid.Empty)

   {

     CustomersBAL bal = new CustomersBAL();

     _selectedCustomer = bal.GetByKey(this.SelectedCustomerKey);

   }

   return _selectedCustomer;

 }

 protected virtual void OnSelectedCustomerChanged(EventArgs e)

 {

   if (SelectedCustomerChanged != null)

     SelectedCustomerChanged(this, e);

 }

}

Figure 1: A custom page class (CustomerPortalPageBase).

 

Now all the pages in the application can implement this custom page class. This won t hurt any of your functionality because CustomerPortalPageBase still inherits from Page; it simply adds another level of inheritance.

This approach has similarities to the Singleton pattern in the respect that the properties and methods defined in the page class are available throughout the application, because controls and user controls maintain a reference to the page class, and can cast its Page property (which returns by default a reference to System.Web.UI.Page) to type CustomerPortalPageBase.

 

Page/User Control Communication

In some situations, the base page class needs to communicate with the user control class on a level that the previous setup doesn t support. Although a page has all its children in the Controls collection, it may be better to store a separate reference to a specific subset of controls that have a special importance in the application.

For instance, the Web part manager knows everything about the Web part zones that are on the same page. This happens because the Web part zones register with the Web part manager on initialization. This is made easy because of the ASP.NET lifecycle, and through a handy property on the Page class, named Items. The Items property returns a dictionary of items created every time the page runs. It s simply an object dictionary that stores anything the child controls of the page want it to. The WebPartManager class stores a reference of itself in this collection, which each Web part zone knows about and uses to register itself with the WebPartManager.

The process I use is very similar, but doesn t make use of the Items property. Instead, the next example uses user control classes (which are controls and can perform the same setup) to register themselves with the page using interfaces at both the page and user control level.

The example I use is a portal page that represents information about the user. This portal page is comprised of customer widgets that represent different information about the customer. These widgets will register themselves with any page that implements the following interface:

 

public interface ICustomerWidgetContainer

{

 void RegisterControl(ICustomerWidget control);

}

 

By having the widgets register themselves with the page, the application saves the processing time it takes to recursively loop through the control collection. To make use of this, the ASP.NET page that contains the widgets adds these controls to an internal collection that it can make use of later. In the process, it would be best to register the widgets at initialization time, so they could be made use of early enough in the lifecycle (see Figure 2).

 

public partial class CustomerDetailsPage : CustomerPortalPageBase,

 ICustomerWidgetContainer

{

 private List<ICustomerWidget> _customerWidgetControls = null;

 protected List<ICustomerWidget> CustomerWidgetControls

 {

   get

   {

     if (_customerWidgetControls == null)

      _customerWidgetControls =

new List<ICustomerWidget>();

     return _customerWidgetControls;

   }

 }

 public void RegisterControl(ICustomerWidget control)

 {

   this.CustomerWidgetControls.Add(control);

 }

}

Figure 2: Customer widget container consumer.

 

What can register itself as a widget could be anything, as long as it s a control. For simplicity, user controls were selected because of the ease of designing them, in addition to no major need to reuse the interface at the current moment.

As always, I like to make development easier, so for user controls that implement the widget interface, I added another base class that user controls can take advantage of, which sits between the UserControl base class and the ASCX code-behind (see Figure 3). This means the application has a central place for code to perform the same functionality. On initialization, the user control registers itself with the parent page, as long as it implements ICustomerWidgetContainer. However, the page doesn t have to; if it doesn t, it simply isn t registered and the page can t take advantage of the registration process.

 

public class CustomerWidgetUserControl :

 UserControl, ICustomerWidget

{

 protected override void OnInit(EventArgs e)

 {

   base.OnInit(e);

   ICustomerWidgetContainer customerContainer =

     this.Page as ICustomerWidgetContainer;

   if (customerContainer != null)

     customerContainer.RegisterControl(this);

 }

}

Figure 3: Customer widget user control base class.

 

So what does this all mean? When the page initializes, the page knows everything about the widgets on the page because they registered themselves with the page. For instance, if there were seven widgets, the collection contains seven object instances (simply because the widgets inherit from CustomerWidgetUserControl).

You may also wonder why use the ICustomerWidget interface; this really isn t necessary. The only reason I used this interface was to constrain what could be registered with the page. It prevents any control, user control, or other object from being registered.

This approach illustrates essentially three features:

  • User controls can have customized base classes.
  • User controls and pages can implement interfaces to increase functionality.
  • Because of the uses of interfaces, the widget could be essentially anything (even a stub or mock class for unit testing).

 

Page Events

When an ASP.NET page executes, the page fires a series of events (referred to as the page s lifecycle) in a specified order. Event firing begins at the page level, then bubbles down to the individual controls. Any events that post back from within a control (custom control events) fire between the load and pre-render events.

Some care is required when managing loading or saving data in these event handlers. Because a page consists of many controls, and user controls that represent sections of the page, updates for different sections of the page occur at different times. Some controls may be updated on initialization, some on load, and some before rendering. Some care is required in case some portions of the application rely on other portions.

When developing applications, I personally believe ASP.NET developers can benefit from the extract method of refactoring, rather than embedding all the code in the OnInit, OnLoad, or OnPreRender page methods. This helps reduce the complexity of the page s code, especially in these event methods. However, sometimes that s not enough. The ASP.NET site needs to respond to state changes, which is often what ASP.NET code is all about. For instance, the page needs to perform an action when the user logs in or logs out. What about the SelectedCustomerChanged event we saw previously? Performing actions when the customer changes data is important, as well.

 

Creating a Custom Event Lifecycle

An event is a response to a change or condition of the state of the application. For instance, the GridView s SelectedIndexChanged event fires when the select link for a different row is clicked. The events in the page lifecycle suffixed with Complete only fire when asynchronous page processing is enabled.

Why not create events in the lifecycle that are dependent on the data? For instance, in our custom page example, the selected customer s key is stored in the cache. Whenever this key value changes to a new customer, an associated event fires. This is, in a sense, creating a custom lifecycle for your application. A custom page class is perfect to implement this because all your ASP.NET pages can inherit from this class, and every control/user control can reference this custom page class through its Page property.

If you ve developed custom controls, this approach is very similar to handling the IPostBackEventHandler interface. In this approach, a control posts back with an event argument (which can be created by Page.ClientScript.GetPostBackClientHyperlink). The control receives the postback and performs its processing at a specific point in the page lifecycle. This point, whenever the event occurs, happens at the same point in the lifecycle.

Similar to custom controls, the key benefits to creating custom lifecycle events are enormous because they reduce the amount of page processing (data queries and if checks) that occur when the page runs. Parts of ASP.NET applications can be read and react (the page looks for a change; when it finds one, it updates the user interface).

However, custom page events help reduce the amount of code by firing a specific event to signify that action; instead of reading and reacting, the code can simply begin its work without doing conditional checks.

In our portal example, the user logs in through the Login control and logs out with the LoginStatus control; this works seamlessly with forms authentication. By firing events for logging in and out at the page level, the control event handlers that perform those actions can simply call two methods to fire these events, and any other code in the page can respond to these. Take a look at the events/methods and control event handlers in Figure 4.

 

public event EventHandler LoggedIn;

public event EventHandler LoggedOut;

protected virtual void OnLoggedIn(EventArgs e)

{

 if (LoggedIn != null)

   LoggedIn(this, e);

}

protected virtual void OnLoggedOut(EventArgs e)

{

 if (LoggedOut != null)

   LoggedOut(this, e);

}

//Login status fires logged out event; call our method to fire our event

protected void lgnStatus_LoggedOut(object sender, EventArgs e)

{

 if (LoggedOut != null)

   LoggedOut(this, e);

}

//Login fires logged in event; call our method to fire our event

void lgnLogin_LoggedIn(object sender, EventArgs e)

{

 this.OnLoggedIn(e);

}

Figure 4: Defining log in/out events and firing them.

 

All pages that inherit from this custom page class can call the OnLoggedIn and OnLoggedOut methods to notify the page of these actions. You may be thinking this approach is not quite as useful. After all, why not let the Login control handle this functionality instead? It fires its own event that the application could respond to, instead of firing a page-level event. In the case of logging in, this may be true; however, I do see one immediate design benefit: code encapsulation.

By defining code for logged in/out status changes in the methods above, any page that requires some action for these events can make use of it without knowing how the user logs in or out of the system. It might not make sense when the log-in capabilities are in one central page, but it makes sense if the Login control resides in the master page for the site. Transform this idea for other areas of the application as well, such as the SelectedCustomerKey property in the custom page class (see Figure 5).

 

public Guid SelectedCustomerKey

{

 get

 {

   if (_selectedCustomerKey == Guid.Empty)

   {

     object value = this.Services.Caching.Get(this.Services.

     Caching.GetSafeKey("SelectedCustomerKey"));

     if (value != null)

       _selectedCustomerKey = new Guid(value.ToString());

   }

   return _selectedCustomerKey;

 }

 set

 {

   if (_selectedCustomerKey != value)

   {

     _selectedCustomerKey = value;

       this.Services.Caching.Add(this.Services.Caching.

        GetSafeKey("SelectedCustomerKey"),

        _selectedCustomerKey);

     this.OnSelectedCustomerChanged(EventArgs.Empty);

   }

 }

}

Figure 5: Tracking selected customer key.

 

When the record for the current customer changes, the SelectedCustomerChanged event fires. This allows the page to respond to this event and refresh any data that displays information about the customer. Figure 6 shows one example for a user control that displays customer orders. When the customer changes, the page refreshes.

 

private void BindOrders()

{

 OrdersBAL bal = new OrdersBAL();

 SamplesDataSet.OrdersDataTable ordersTable =

   bal.GetCustomerOrders(((CustomerPortalPageBase)

  this.Page).SelectedCustomer);

 this.gvwOrders.DataSource = ordersTable;

 this.gvwOrders.DataBind();

}

protected override void OnSelectedCustomerChanged(EventArgs e)

{

 base.OnSelectedCustomerChanged(e);

 this.BindOrders();

}

Figure 6: Binding orders in response to customer change.

 

Although state changes are nice, they aren t always necessary. For instance, we talked about how to handle changes to the current customer s record. In some instances, it s just as easy to rewrite the information to the screen. For instance, the customer portal sample displays the basic information about the selected customer in a user control. This user control simply accesses the SelectedCustomer record in our custom page class and writes the information to its controls. Though it could respond to a selection change, does it always have to? Because the record is cached, it s not as critical to respond to the selection change, and simply change the data on pre-rendering, as shown in Figure 7.

 

protected override void OnPreRender(EventArgs e)

{

 base.OnPreRender(e);

 CustomerPortalPageBase page = this.Page as CustomerPortalPageBase;

 if (page == null)

   throw new Exception("This control is not on a page that inherits

                       from the correct base class");

 if (page.SelectedCustomer != null)

 {

   this.lblFirstName.Text = page.SelectedCustomer.FirstName;

   this.lblLastName.Text = page.SelectedCustomer.LastName;

   this.lblAccountNumber.Text = page.SelectedCustomer.AccountNumber;

    this.mvwCustomerDetails.ActiveViewIndex = 1;

 }

 else

   this.mvwCustomerDetails.ActiveViewIndex = 0;

}

Figure 7: Displaying customer data in pre-render.

 

Loosely Coupled Approaches

If you ve read books on object-oriented design or design patterns, like Code Complete by Steve McConnell, Design Patterns by Gamma et al., or one of the many others, you may have read about developing business components that are less coupled with each other. What this means is that the more the system can work with abstraction, the less development effort there needs to be to create a solution or change it in the future.

I mentioned that some of the custom events added to the custom page lifecycle could be a LoggedOut event, which responds to a log-out request in the system. Suppose that log-out mechanism is through the use of the LoginStatus control. The placement of this control is in the master page. My goal in this section is to not couple the LoginStatus control to the master page, which the page will make use of. Rather, through some abstraction, I m going to make this as loosely coupled as I can.

Looking at a more tightly coupled approach, I easily could have embedded an event handler in the master page that responds to logging the user out of the system, as shown here:

 

protected override void OnInit(EventArgs e)

{

 base.OnInit(e);

 LoginStatus logoutControl =

   this.Page.Master.FindControl("LoginStatus1")

  as LoginStatus;

 logoutControl.LoggedOut += LoggedOutHandler;

}

 

To avoid that coupling, I need the use of an interface. This interface fires an event to let the page know the user logged out. Essentially, this event lets me bubble up a logged-out event that the page attaches to, and fires its own LoggedOut event. I ve included the definition of the event in Figure 8, as this interface is implemented for the master page.

 

public interface ILoggingOutControl

{

 event EventHandler LoggedOut;

}

public partial class Site : System.Web.UI.MasterPage, ILoggingOutControl

{

 public event EventHandler LoggedOut;

   protected void lgnStatus_LoggedOut(object sender, EventArgs e)

 {

   //Bubbles up the logged out event from login status

   //to master page, which bubbles up to the page

   if (LoggedOut != null)

     LoggedOut(this, e);

 }

}

Figure 8: Master page implementation of IloggingOutControl.

 

The CustomerPortalPageBase class has a RegisterLoggingOutControl method (see Figure 9). This method takes a reference to an object with the new interface and attaches to the event. This finishes the event bubbling process.

 

public void RegisterLoggingOutControl(ILoggingOutControl control)

{

 if (control == null)

   throw new ArgumentNullException("control");

 control.LoggedOut += LoggedOutHandler;

}

Figure 9: Custom page class tapping into LoggedOut event.

 

Using an interface avoids passing in a reference to a specific control; I could have passed in the LoginStatus control reference instead, because this control does define that event. However, that poses two problems. First, if I change the parameter of that method from the ILoggingOutControl type to LoginStatus type, the application will require a LoginStatus control as the sole interface control to log the user out of the system which is not what I m trying to accomplish.

I could have made the parameter a reference to an object of type Control; however, the Control class itself doesn t define the LoggedOut event, which would require me to use reflection to look for a LoggedOut event. While I do like reflection and see some great benefits with it, it does add some performance overhead, and I was trying to stay away from that.

Because the master page implements this interface, the last requirement is to change the initialization method to pass in a reference to the master page to the RegisterLoggingOutControl method. The new OnInit definition is shown in Figure 10.

 

protected override void OnInit(EventArgs e)

{

 base.OnInit(e);

 CustomerPortalPageBase page = this.Page

   as CustomerPortalPageBase;

 if (page != null)

   page.RegisterLoggingOutControl(this);

}

Figure 10: Registering the master page with the custom page class.

 

It may require more code, but this solution is a little more flexible and abstract. This implementation even allows multiple controls to log the user out of the system. To take the abstraction to a different level and reduce the amount of code in the master page, another solution is to create a wrapper for the LoginStatus control, which implements the ILoggingOutControl interface (see Figure 11).

 

public class LoginStatusWrapper : ILoggingOutControl

{

 private LoginStatus _loginStatus = null;

 public event EventHandler LoggedOut;

 public LoginStatusWrapper(LoginStatus loginStatus)

 {

   if (loginStatus == null)

     throw new ArgumentNullException("loginStatus");

   _loginStatus = loginStatus;

   _loginStatus.LoggedOut += LoginStatusWrapper_LoggedOut;

 }

 protected virtual void OnLoggedOut(EventArgs e)

 {

   if (LoggedOut != null)

     LoggedOut(this, e);

 }

 void LoginStatusWrapper_LoggedOut(object sender, EventArgs e)

 {

   this.OnLoggedOut(e);

 }

}

Figure 11: Using a wrapper class to implement the log-out interface.

 

The master page can pass in a reference of type LoginStatusWrapper to the RegisterLoggingOutControl, by passing in a reference to a new LoginStatusWrapper(this.lgnStatus). A wrapper is a useful pattern because it can extend an existing class (even if that class is sealed), providing extra functionality not originally present.

 

Specialized Development

With a global page class, you may wonder if it s worth it to use these concepts in other areas of development. Specialized page classes used for not-so-widely scaled areas of the application can be a benefit. After all, the more code that exists in a Web class library, the more testable the application is. What I mean by that is that unit tests can actually instantiate the page and test out the various properties or methods, which is a benefit to the application.

However, I d advise caution when it comes to specialized page or user control classes. Putting too much logic in custom page classes can create a maintenance nightmare, especially if the code controls the entire page from the code-behind. It s more verbose to write code in the code-behind than set values in the ASPX markup.

I mention the avoidance of this because customized page classes tend to grow in size and become unmanageable if you try to incorporate all the potential logic in the code-behind class. Splitting the logic between the custom page class and the ASPX class can be a challenge to remember what code was implemented where. I tried this approach early in my development career, incorporating as much logic into the page class as possible to facilitate unit testing I even wrote an article on the subject (see ASP.NET OOP and Unit Testing at http://aspalliance.com/1328).

Moving logic to a code-behind page class for two ASPX pages that perform mostly the same function or putting a few methods into a specialized page that s reused across a subset of ASPX pages can be a good idea. However, trying to implement all your logic for an ASPX page in a code-behind class that resides in a class library solely for the purpose of unit testing can be a maintenance nightmare. The point I m trying to make with this paragraph is to find a balance between the two options.

I m not against writing specialized custom class pages that are only used by one to five pages or so. Although it s good to have a common set of features available in one centralized area, on occasion this can be accomplished through helper classes defined as static, which can be created in a class library or somewhere else. A static helper class facilitates reuse and testability, while not making the application more overtly complex. Interfaces also can help offset some of the challenges, as well.

 

Concurrency/Caching Issues

In the previous custom page example, the key of the customer resides in cache, accessible through the base class. This allows the pages to retrieve the information about the customer. The customer record itself could have been stored in cache instead; this would have provided direct access to the data, with a potential cost.

The questions you must think about when designing data-driven applications are:

  • How frequently do data changes occur?
  • Is data generally static or dynamic?
  • Do data changes occur in real-time fashion?

 

There aren t any easy answers to these questions. If the data changes a lot, it might be best to store the key in the page class. The downside to this is the customer record is re-queried to provide the same customer information during multiple page loads, potentially. In addition, caching the record may be beneficial if only one user changes the details about that customer; however, if many users change the customer record, it s better to re-query the data every time, with adequate locking to ensure integrity of the data.

Certain architectures may be more of a challenge with these issues; for instance, LINQ to SQL requires a current data context, and data cached outside that current context cannot be used in conjunction with the data context. If you do go against the data context with data that was queried in a past lifetime of the data context, an exception is thrown. However, you can access the object s properties and relationships like any other business object.

In addition, what happens when the user logs out of the system, or closes the browser? Although there are only so many details you can handle inside the Web browser, the question about this issue is, should the data remain in cache when the user isn t accessing the site, and for how long?

 

Conclusion

This article covered a variety of topics focused on developing logic in business applications. It looked at using a custom page class to facilitate the flow of the application, and acts as sort of a Singleton class. It also touched on the use of interface to open up communication between the page and user control. I like the interface option when developing in ASP.NET because it allows for testability, along with custom page classes. Putting more logic in the page class (that exists in a class library) can aid testability; however, more logic in the page class starts to make that class more specialized for certain areas of the application, which can hurt maintainability.

C# and VB.NET source code accompanying this article is available for download.

Brian Mains is a consultant with Computer Aid Inc., where he works with non-profit and state government organizations. He was awarded the Microsoft Most Valuable Professional award in July 2007 and 2008 and has been active on several .NET forums and Web sites. You can catch him on his blog at http://dotnetslackers.com/Community/blogs/bmains/default.aspx.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish