CoverStory
LANGUAGES:
C# | VB.NET
ASP.NET
VERSIONS: 3.5
Data Application Tricks
Performance, Reliability, and Accuracy
By Brian Mains
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.