Creating a generic Message Display Page
for ASP.Net
By Rick Strahl
www.west-wind.com
rstrahl@west-wind.com
Last Update:
July 08, 2004
Source Code for this article:
http://www.west-wind.com/presentations/wwMessageDisplay/wwMessagedisplay.zip
Creating messages in your Web application should be quick and
easy and most importantly, consistent. They should look like they belong with
the rest of the application even if – low and behold – an error occurs. How
often have you created a new page to display some simple text or a notification
message to your users? Wouldn’t it be much nicer if you could reuse an existing
template and simply passed in a few parameters to tell it to render an
application specific message? In this article Rick shows how to create a
reusable Message Display class that reduces displaying messages generically in
you application to a single line of code.
ASP.Net reduces the need for generic message pages
considerably with its PostBack based metaphor. Because most pages post back to
themselves rather than going off to other pages, messages can in many cases be
re-displayed on the original page along with a message or error header. Not only
is this convenient to code with .Net by simply adding a label or other display
control to show the message, but it's also much nicer for the user who sees the
message in the context of the operation that that produced the error or action
that caused the message.
However, there are still a number of occasions where I find
the ASP.Net PostBack mechanism doesn't lend itself well for message and
especially error display. In these situations a generic message display page
comes in very handy. By generic message display I’m referring to a simple
reusable page template that can be called easily as a single method call by
passing a couple of parameters. I use it for these types of situations:
- Global Error Messages
Errors that you capture in Global.asax generally will end up displaying
a generic error page. Rather than redirecting to a completely static page, I
can provide some customized info via a generic display page that may contain
a semi-static message, or for administrators detailed error information.
- Simple Confirmation Pages
There are still a number of forms that do well with simply having a
confirmation page that tells you – “Thank you, M'am for your submission”.
ASP. Net always wants you to post back to the same page but in many
instances there’s nothing to display when you go back. For example, if you
delete a record or you need to display a simple, but lengthy generated
result. Although you can do this with a Redirect() you really don’t want to
create a new form for each of those messages, nor do you always want to just
go somewhere else without giving the user some feedback first.
- Redirection Situations where you need Cookies
In my applications there are a number of login scenarios where users are
either automatically logged in or logged in through a login form. Although
Forms Authentication can handle some of these scenarios it’s often not
granular enough to provide functionality needed. In login situations I need
to forward to a new page, but at the same time need to store a Cookie (for
permanent record), so I can't use a Response.Redirect(). Instead I use an
intermediate Display page that includes a META refresh tag to automatically
or optionally via link handle the forwarding while still being Cookie
friendly.
- Quick Dynamic Display of Html Data
Because this class is using predefined controls, it's easy to just pass
in just a header and a message – optionally in HTML format to very easily
create an HTML display. I can use this for debugging scenarios or while
testing code.
Using the class
The primary purpose of this class and template is to
display standalone messages easily and consistently from within your
application. To implement this functionality I created a generic reusable class
that must be subclassed for each application you build. The base class consists
of a non-visual WebForm class and it relies on the concrete implementation and
the ASPX template of the subclassed WebForm to provide its visual interface.
The concept is simple: You add a new WebForm to your
application and subclass it from the wwMessageDisplay base class. The Html
layout/design for your message page is customized to your application and
reflects your own theme and layout, plus it must add a few required labels that
are used to dynamically fill the data that is to be displayed – the header and
body as well as the title and optional redirection directives. Beyond that the
form can be of your own design to match your Web application’s look and feel.
Figure 1 shows an example of an application specific error message displayed in
my Web Store application for the West Wind site that uses the MessageDisplay
form created for the application.

Figure 1 – Showing a generic message page. A single method call to the
DisplayMessage() method with a Header and Message body is all it takes to
generate a parameterized, generic message page from anywhere in the Web
application.
Once you’ve designed your HTML form and subclassed it from
wwMessageDisplay, calling this custom message page is as simple as making a
static method call with at least two parameters to specify the header and
message. Assuming the custom class and ASPX page is called the default
MessageDisplay (MessageDisplay.aspx) you can call like this from anywhere in
your application:
MessageDisplay.DisplayMessage("Application Error",
"An <b>error</b> occurred in the application. blah module…");
You can call this from anywhere: From another ASPX page
event, from within an ASPX page expression, and even from an HTTP handler. The
call will take care of the current request displaying the message display page
and then exit. You don’t need to issue Response.End() to finish as it’s implicit
in the DisplayMessage() code.
You pass in a header message and the body for the message.
Both can contain HTML markup to allow you to embed links or formatting if
necessary as Figure 1 does with the link back to the Home Page. Additional
overloads of the DisplayMessage() method allow you to specify an explicit ASPX
page (in case you have more than one ‘message template page’ in your
application) or you can pass in a URL to automatically redirect to after a
specified interval.
As you can see in Figure 1 the page is customized with the
look and feel of the main application – it has the toolbar, links and page
footer that the original application shows. This is provided by the custom
WebForm the developer creates which is required to contain at least the two
controls – Header and Message in order to work. The template Html in this
example includes several User Controls (the toolbar, footer and links boxes),
but you could just as well handcode all of the Html.
In order for the information to display the Html Template
(your .ASPX page) requires that a couple of expressions and a couple of label
controls exist in the page. Listing 1 shows the ASPX HTML source for the page
above. The required portions are marked in bold.
<%@ Page language="c#"
Codebehind="MessageDisplay.aspx.cs" Inherits="Westwind.WebStore.MessageDisplay"
AutoEventWireup="false" enableViewState="false" %>
<%@ Register TagPrefix="ww"
TagName="CategoryList" src="CategoryList.ascx"%>
<%@Register TagPrefix="ww"
TagName="PageHeader" src="PageHeader.ascx"%>
<%@Register TagPrefix="ww"
TagName="PageFooter" src="PageFooter.ascx"%>
<HTML>
<HEAD>
<title>
<%=
this.Header %>
</title>
<%=
this.RedirectMetaTag %>
<link
REL="stylesheet"
TYPE="text/css"
HREF='<%=
this.BasePath %>/wwWebstore.css'>
<base
href='<%=
this.BasePath %>/'
>
</HEAD>
<body
TOPMARGIN="0"
LEFTMARGIN="0">
<!--
West Wind Menu -->
<ww:PageHeader
RUNAT="server"
ID="oMenu"
/>
<table
BORDER="0"
CELLSPACING="0"
CELLPADDING="0"
WIDTH="100%"
CLASS="body"
HEIGHT="100%">
<tr>
<td
class="categorylistbackground"
valign="top"><br>
<ww:categorylist
RUNAT="server"
ID="oCategoryList"
/>
</td>
<!--
Custom Form Stuff -->
<td
VALIGN="top"
BGCOLOR="#ffffff"
CLASS="body"
WIDTH="*">
<form
ID="form1"
RUNAT="server">
<br>
<table
border="0"
width="97%">
<tr>
<td
class="gridheader"
align="center"
height="35">
<asp:label
ID="lblHeader"
RUNAT="server"></asp:label>
</td>
</tr>
<tr>
<td>
<br>
<blockquote>
<asp:label
ID="lblMessage"
RUNAT="server"></asp:label>
<br>
<p></p>
</blockquote>
<center><small>
<asp:label
ID="lblRedirectHyperLink"
RUNAT="server"></asp:label>
</small></center>
<ww:PageFooter
id="oFooter"
runat="server"></ww:PageFooter>
</td>
</tr>
</table>
</form>
</td>
</tr>
<!--
End Custom Form Stuff -->
</table>
</body>
</HTML>
Only a few things are specific to the MessageDisplay
routine:
lblHeader Label
This is where the header label text is stored. In Figure 1 this is the
Application Error message in the blue bar.
lblMessage Label
This is the label that receives the main message text. This text should be in
Html format or plain text (in which case it just flows as a single paragraph).
lblRedirectHyperLink Label
If you’re using a Redirect link this label is used to show the page you are
redirecting to. The page should automatically redirect to this link when the
timeout is hit, but if it doesn’t (if the browser doesn’t support it or has it
disabled) it’s there to click on.
<title> Tag
Usually you’ll want to set the Title of the page to display your main
message. To do so. use the this.Header member and embed it into the <title> tag.
Each of the passed in parameters are available here as properties: Header,
Message, RedirectUrl and also RedirectMetaTag. You can use these anywhere you
need.
this.BasePath
This property contains the base path of the Web application as a fully
qualified URL like http://www.west-wind.com/WebStore/. A base Url is quite
important if you need to display messages out of multiple directories. Without a
<base> tag in the HTML header images might not be found since relative paths can
easily break. The <base> above points back to the application root from which it
can find any relative links. Here this.BasePath is used to reference the style
sheet and set the <base> tag.
The above describes the contents of the .ASPX page (MessageDisplay.aspx)
Html template. You also need a little bit of code to hook up the page logic.
This basically consists of forwarding the standard page processing to a helper
method that parses out the values passed in and assigning them to the
appropriate controls. A full codebehind implementation of the class looks like
this:
public
class MessageDisplay :
Westwind.Web.Controls.wwMessageDisplay
{
protected
System.Web.UI.WebControls.Label lblHeader;
protected
System.Web.UI.WebControls.Label lblMessage;
protected
System.Web.UI.WebControls.Label lblRedirectHyperLink;
private
void Page_Load(object
sender, System.EventArgs e)
{
this.DisplayPage();
}
}
The key here is the DisplayPage() method which is inherited from the base class
and is responsible for picking up the values passed into the static
DisplayMessage() method. It looks for the above mentioned controls and populates
them with the values retrieved from its internal state before the page is
displayed.
How it works
Prior to ASP.Net I had always had a function like this in
my toolbox because it was a requirement in so many places to dump a quick
message to the user. In those days the idea was simple enough: Generate some
code on the fly from within the source code and eventually call Response.Write()
or equivalent to dump the hand generated HTML to the Http output stream. ASP.Net
makes this process much cleaner by providing a clear Page processing model, the
ability to transfer control to another page and pass context information to this
page. Because of this it’s now much easier to create a new subclass that
includes a template that describes the display formatting (the .ASPX template).
The implementation is based on a base class that provides
all the processing functionality and a subclass you implement to provide the
display template plus any additional logic that you might need to fire when the
page runs.
The flow of this process is shown in Figure 2. You simply
call the static DisplayMessage() method on your subclass from anywhere in your
application with at least two parameters of Header and Body. This method then
handles calling your ASPX page by using the Server.Transfer() mechanism and the
Context object’s state mechanism to pass it the input parameters.

Figure 2 – The static DisplayMessage method
accepts simple parameters and uses the HttpContext object to pass the values to
the ASP.Net page handler for processing via the Server.Execute() method. This
process allows a single method call to cause an ASP.Net page to be generated
from anywhere in the application.
The .ASPX page is then run as a live instance of your
subclass. Your only task is to call this.DisplayPage() which inherits from the
base class. This method contains the logic to retrieve the data stored in the
Context object, populate properties on the form and then renders the ASPX page.
The key concept is ASP.Net's ability to transfer control
from one request to another using the Server.Transfer() mechanism. If
you’re not familiar with Server.Transfer() it provides the ability to stop
execution of the current request and jump over to another page in the same
application and process it. All Request information (except the URL which is
adjusted for the Transfers new Url) are kept intact so when you jump over to the
new page your Request.Form and Request.ServerVariables still contain the same
content as they had on the original page.
Keeping Context
What ties the original page or request and the transferred
page together is the Context object. The Context object - as the name implies –
has the lifetime of the current request. All of ASP.Net's intrinsic objects like
Request, Response, Session etc. are actually members of the Context object.
Think of the Context object as the main object that passes through the current
request from beginning to end. The Context object also includes a state bag that
can be used to store values into so they can be picked up at a later point in
processing, which is exactly what is needed to pass information over a
Server.Transfer() call. Using Context.Items["key"] values essentially serves as
the parameter interface for Server.Transfer().
Implementation
To provide this functionality I used a subclass of the Page
class that implements both a static and public interface to handle the generic
tasks of message creation. The static methods are called from your application
and are static to let you easily call the methods without any setup. The
instance properties and methods are called only from within running WebForm.
This way the two interfaces have clear separation as external for the static and
internal for the instance.
Message display is a two step process: Initiating the
request with a simple static method call, then transferring control to your
custom class and letting ASP.Net render the template.
Let’s start with the static call interface. Listing 3 shows
the full parameter list DisplayMessage method implementation and its overloads
which can be called from anywhere in your ASP.Net application.
public
class wwMessageDisplay : System.Web.UI.Page
{
public
static string
Pagename = "MessageDisplay.aspx";
public
static void
DisplayMessage(string TemplatePageName,
string
Header, string Message,
string
RedirectUrl, int Timeout)
{
HttpContext Context = HttpContext.Current;
Context.Items.Add("ErrorMessage_Header",Header);
Context.Items.Add("ErrorMessage_Message",Message);
Context.Items.Add("ErrorMessage_Timeout",Timeout);
Context.Items.Add("ErrorMessage_RedirectUrl",RedirectUrl);
Context.Server.Transfer(Context.Request.ApplicationPath +
"/" +
TemplatePageName);
}
public
static void
DisplayMessage(string Header,
string Message,
string RedirectUrl,
int Timeout)
{
DisplayMessage(Pagename,Header,Message,RedirectUrl,Timeout);
}
public
static void
DisplayMessage(string Header,
string Message)
{
DisplayMessage(Header,Message,null,0);
}
}
The DisplayMessage() method simply accepts a set of
parameters and stuffs all of them except the TemplatePageName into the Context
object. The full function version includes a first parameter of the
TemplatePageName in case you don’t want to use the default name specified in the
Pagename static property. This means if you implement your page as
MessageDisplay.aspx you can use one of the other overloads that omits the page
name. Why am I passing the page name? A static method has no way to determine
the name of the currently access class and so the name of the page/class needs
to be stored somewhere (Pagename) or be passed in as a parameter so that we can
transfer to the correct page. If you have more than one message page in your
application you’ll want to use the TemplatePageName to specify it excplicitly.
Note the use of the HttpContext.Current reference to get a
reference to the ASP. Net intrinsic objects including Context and Request.
HttpContext.Current is always available to an ASP.Net request (except in
designmode) and provides a great way to access all the ASP.Net object in generic
methods like this one without having to explicitly pass in either Request,
Response or even the Context object.
The class contains additional methods and properties that
are non-static that provide the page execution behavior. The non-static
interface shown in Listing 4 shows the internal interface used to fill the
controls of the WebForm.
protected
string BasePath =
"";
protected
string RedirectMetaTag =
null;
protected
string Header
{
get {
return (string)
Context.Items["ErrorMessage_Header"]; }
}
protected
string Message
{
get {
return (string)
Context.Items["ErrorMessage_Message"]; }
}
protected
string RedirectUrl
{
get {
return (string)
Context.Items["ErrorMessage_RedirectUrl"];
}
}
public
void DisplayPage(Label Header,Label
Message,Label RedirectHyperLink)
{
string Port
= Request.ServerVariables["SERVER_PORT"];
if (Port ==
null || Port ==
"80" || Port ==
"443")
Port = "";
else
Port = ":"
+ Port;
string
Protocol = Request.ServerVariables["SERVER_PORT_SECURE"];
if (Protocol
== null || Protocol ==
"0")
Protocol =
"http://";
else
Protocol =
"https://";
// *** Figure out the
base Url which points at the application's root
this.BasePath
= Protocol + Request.ServerVariables["SERVER_NAME"]
+
Port + this.ResolveUrl(Request.ApplicationPath);
Header.Text = this.Header;
Message.Text = this.Message;
if (this.RedirectUrl
!= null)
{
string
NewUrl = this.RedirectUrl;
//
Must fix up the path in case we're in a separate
sub-dir
//
because the page is using <base>
we must include full relative path
if (NewUrl.StartsWith("~")
|| NewUrl.StartsWith("/") )
NewUrl =
this.ResolveUrl(NewUrl);
else
if ( !NewUrl.ToLower().StartsWith("http:")
&&
!NewUrl.ToLower().StartsWith("https:")
)
{
// Relative
Path. Must use current server path + relative path
NewUrl = Request.FilePath.Substring( 0,
Request.FilePath.LastIndexOf("/")
+ 1) +
NewUrl;
}
this.RedirectMetaTag
= string.Format(
"<META
HTTP-EQUIV='Refresh' CONTENT='{0}; URL={1}'>\r\n",
this.Context.Items["ErrorMessage_Timeout"],NewUrl);
RedirectHyperLink.Text =
"<a href='" + NewUrl +
"'>Click
here</a> if your browser…";
}
}
public void
DisplayPage()
{
this.DisplayPage((Label)
this.FindControl("lblHeader"),
(Label)
this.FindControl("lblMessage"),
(Label)
this.FindControl("lblRedirectHyperLink"));
}
This part of the interface consists of a few properties
that simply pull their values out of the Context object for easier access inside
the ASPX page code and the DisplayPage method which is responsible for parsing
the context values into the appropriate placeholder Label controls of your page.
Notice that the DisplayPage() method has two overloads –
one with no parameters and one with a set of control references for the controls
that are to be updated on the page. The parameterless version uses
Page.FindControl() to create references to the common names of the controls.
Header.Text and Message.Text are simply assigned from the Header and Message
properties which return the corresponding Context collection values.
Most of the DisplayPage code deals with fixing up URLs to
display properly. Dealing with the Urls is a bit tricky, because this page can
be loaded from different locations in your application. The page can be loaded
from the root of the application or one of its subdirectories which makes it
difficult to determine relative paths of links, stylesheets, images etc. For
this reason the HTML template includes a <BASE> tag in the page to force all
related links relative to this base URL. Things are further complicated that
some URLs are bound by this base tag and others not – for example redirect tag
and style sheet references in the header since <BASE> doesn’t apply to the
header.
There are two paths that are used: The path for the <base>
tag which is a full URL including protocol and port, and the path that is used
for the redirection link if provided which must be server relative. Creation of
the base path invovles taking the Request.ApplicationPath and prepending
protocol, server and port information. The redirection Url is more work as you
have to deal with absolute URLs, relative links or links that use ASP. Net’s
‘root’ convention (using the ‘~’ character to signify the application root). The
Control.ResolveUrl() method can easily take care of ‘~’ parsing and converting
that into a application relative Url. If the URL is absolute and includes
protocol prefix and port we don’t need to do anything else and just use it as
is. If the path is relative, we have to start with the page’s current path and
then add the relative path to it. Unfortunately ASP.Net has no mechanism to
return you just the path of the current request so we have to parse it out of
the full script path using the Request.FilePath property. All of this takes care
of parsing the URL for redirection and the <base> tag.
You’ve got messages!
And voila! You now have a generic message display
implementation that can be called from anywhere in your application with a
single method call. You can even set up multiple message display pages by using
providing a specific page name to display. As long as the pages derive from
wwMessageDisplay and they include the required controls to display the header
and messages you’re ready to go.
I use this class all over the place. All of my error
handling goes through this class as well as many administrative forms that do
not work well for the Postback mechanism. For example, many of my apps include
delete functionality where the main operations are handled through plain
postback, but at the end of a delete operation I display message of the final
status that lets the user know that the operation has completed:
MessageDisplay.DisplayMessage("Invoice Deleted",
"Invoice Number: " + Invoice.InvNo +
"<hr>has been deleted.",
"InvoiceList.aspx",5);
This is much nicer than just redirecting – you’re giving
some feedback first, then going to another location. Also in this case where a
deletion occurs it’s hard to post back to the current page – the invoice no
longer exists, and we can’t display a message on that invoice page itself since
there’s nothing left to show. Instead we have to go somewhere else and it’s nice
to be notified first that the operation completed before moving on. Being able
to do this with a couple of lines of code is very convenient.
I hope that this article has given you a few tips and
insides in how you can build front end routines to ASP.Net pages. In essence,
I’ve shown a generic way to call an ASPX page through a simple method. If you
look at this approach you might find a few other places where a single method
interface provides efficient access to frequently used pages that require
dynamic data to be passed in and displayed.
As always if you have any questions or comments you can contact
me on our message board at:
http://www.west-wind.com/wwThreads/Default.asp?Forum=White+Papers
|