Table of contents
This is the fourth in a series of articles on a class library for ASP.NET applications that I have developed. It contains a set of common, reusable page classes that can be utilized in web applications as-is to provide a consistent look, feel, and set of features. New classes can also be derived from them to extend their capabilities. The features are all fairly modular and may be extracted and placed into your own classes too. For a complete list of articles in the series along with a demonstration application and the code for the classes, see Part 1 [^].
This article describes the only non-page derived class in the library, PageUtils
, along with the remaining methods of the BasePage
class that are somewhat similar in nature to those contained in it. PageUtils
contains a set of utility functions that you may find useful in any ASP.NET application. Each of the features is described below. The class itself is sealed and all public
properties and methods are static
. As such, the constructor is declared private
as there is no need to instantiate the class.
The first method presented is HtmlEncode
, which can be called to encode an object for output to an HTML page. It encodes any HTML special characters as literals instead of letting the browser interpret them. In addition, it replaces multiple spaces, tabs, and line breaks with their HTML equivalents thus preserving the layout of the specified text. The size of expanded tab characters can be altered using the TabSize
property. Set it to the number of non-breaking spaces that should replace the tab character. The default is four.
If the object is null
(Nothing
), results in an empty string, or is a single space, a non-breaking space is returned. In conjunction with the above-described behavior, this is useful for displaying database fields that contain HTML special characters, formatting, or nulls such as those with the text or memo data type.
As an added bonus, if the encodeLinks
parameter is true, URLs, UNCs, and e-mail addresses are converted to hyperlinks whenever possible using the EncodeLinks
method (see below). If false, they are not converted and will be rendered as normal text. As shown below, the code is fairly simple and requires little in the way of additional explanation:
public static string HtmlEncode(Object objText, bool encodeLinks)
{
StringBuilder sb;
string text;
if(objText != null)
{
text = objText.ToString();
if(text.Length != 0)
{
if(expandTabs == null)
expandTabs = new String(' ',
PageUtils.TabSize).Replace(" ", " ");
sb = new StringBuilder(
HttpUtility.HtmlEncode(text), 256);
sb.Replace(" ", " ");
sb.Replace("\t", expandTabs);
sb.Replace("\r", "");
sb.Replace("\n", "<br>");
text = sb.ToString();
if(text.Length > 1)
{
if(!encodeLinks)
return text;
return PageUtils.EncodeLinks(text);
}
if(text.Length == 1 && text[0] != ' ')
return text;
}
}
return " ";
}
The second method presented is EncodeLinks
. This method is called by HtmlEncode
but can also be called directly by your code. It takes the passed string and finds all URLs, UNCs, and e-mail addresses and converts them to clickable hyperlinks suitable for rendering in an HTML page. For UNC paths, it will include any text up to the first whitespace character. If the path contains spaces, you can enclose the entire path in angle brackets (i.e., <\\Server\Folder\Name With Spaces>) and the encoder will include all text between the angle brackets in the hyperlink. The angle brackets will not appear in the encoded hyperlink:
public static string EncodeLinks(string text)
{
if(reURL == null)
{
reURL = new Regex(@"(((file|news|(ht|f|nn)tp(s?))://)|" +
@"(www\.))+[\w()*\-!_%]+.[\w()*\-/.!_#%]+[\w()*\-/" +
@".!_#%]*((\?\w+(\=[\w()*\-/.!_#%]*)?)(((&|&(?" +
@"!\w+;))(\w+(\=[\w()*\-/.!_#%]*)?))+)?)?",
RegexOptions.IgnoreCase);
reUNC = new Regex(@"(\\{2}\w+(\\((&.{2,8};|" +
@"[\w\-\.,@?^=%&:/~\+#\$])*[\w\-\@?^=%&/~\+#\$])?)" +
@"*)|((\<|\<)\\{2}\w+(\\((&.{2,8};|" +
@"[\w\-\.,@?^=%&:/~\+#\$ ])*)?)*(\>|\>))",
RegexOptions.IgnoreCase);
reEMail = new Regex(@"([a-zA-Z0-9_\-])([a-zA-Z0-9_\-\." +
@"]*)@(\[((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]" +
@"[0-9]|[0-9])\.){3}|((([a-zA-Z0-9\-]+)\.)+))(" +
@"[a-zA-Z]{2,}|(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|" +
@"[1-9][0-9]|[0-9])\])", RegexOptions.IgnoreCase);
reTSUNC = new Regex(
@"\.?((&\#\d{1,3}|&\w{2,8});((&\#\d{1,3}|&" +
@"\w{2,8}))?)+\w*$");
urlMatchEvaluator = new MatchEvaluator(OnUrlMatch);
uncMatchEvaluator = new MatchEvaluator(OnUncMatch);
}
text = reURL.Replace(text, urlMatchEvaluator);
text = reUNC.Replace(text, uncMatchEvaluator);
text = reEMail.Replace(text,
@"<a href='mailto:$&'>$&</a>");
return text;
}
As you can see, the method uses regular expressions to search for and replace each URL, UNC, and e-mail address. The expressions used should catch just about all variations of each type. The regular expression objects are created on first use and are kept around for subsequent calls to save a little time. For URLs and UNCs, the following match evaluators handle the actual work of the replacement:
private static string OnUrlMatch(Match match)
{
StringBuilder sb = new StringBuilder("<a href='", 256);
string url = match.Value;
if(url.IndexOf("://") == -1)
sb.Append("http://");
sb.Append(url);
sb.Append("' target='_BLANK'>");
sb.Append(url);
sb.Append("</a>");
return sb.ToString();
}
private static string OnUncMatch(Match match)
{
StringBuilder sb = new StringBuilder("<a href='file:", 256);
string unc = match.Value;
if(unc[0] == '<')
unc = unc.Substring(1, unc.Length - 2);
else
if(unc.StartsWith("<"))
unc = unc.Substring(4, unc.Length - 8);
Match m = reTSUNC.Match(unc);
if(m.Success == true)
unc = reTSUNC.Replace(unc, "");
sb.Append(unc);
sb.Append("' target='_BLANK'>");
sb.Replace('\\', '/');
sb.Append(unc);
sb.Append("</a>");
if(m.Success == true)
sb.Append(m.Value);
return sb.ToString();
}
A regular expression match evaluator is like a callback. Each time the regular expression finds a match, it calls the evaluator. Its job is to take the found text and modify it in any way necessary and then return it to the regular expression so that it can be used to replace the original text. In these two cases, the match evaluators add the anchor tag and ensure that the links are formatted appropriately.
In my applications, I have come to favor the validation summary control to contain all validation error messages generated by the page. It keeps them all in one location and does not adversely affect the layout of the controls in the form when they are made visible. The drawback is that on a form with a large number of controls and validation conditions, it can sometimes be difficult to match each message to its control, especially if the form is long enough to require scrolling around to find it. As such, I have added functionality to the BasePage
class to automatically convert all validation control error messages that are set to appear in a validation summary control to clickable hyperlinks that will take you directly to the offending field by giving it the focus:
protected virtual void ConvertValMsgsToLinks()
{
BaseValidator bv;
foreach(IValidator val in this.Validators)
{
bv = val as BaseValidator;
if(bv != null && bv.Visible == true &&
bv.ControlToValidate.Length > 0 &&
bv.Display == ValidatorDisplay.None)
bv.ErrorMessage = MakeMsgLink(bv.ControlToValidate,
bv.ErrorMessage, this.MsgLinkCssClass);
}
}
A call to ConvertValMsgsToLinks
is done as the very first step in the overridden Render
method. It iterates over the page's Validators
collection. The validator control must be visible, must have its ControlToValidate
property set to a control ID, and must have its Display
property set to None
indicating that it will appear in a validation summary control. If all of the necessary conditions are met, a call is placed to the MakeMsgLink
method to convert the error message to a hyperlink.
Note that since this occurs within the rendering step, changes to the error messages are not retained. If the page posts back, the error messages are restored from view state and will be in their non-hyperlink form. When the page renders during the postback, the messages will be converted to hyperlinks again provided that they still meet the necessary conditions. I chose this approach so that it is transparent to users of the class, is non-intrusive, and will not break any code that expects the messages to be in their non-hyperlink form. Derived classes can override this method to extend or suppress this behavior.
Note: If extracting the above method for use in your own classes, be sure to override the page's Render
method and call it. If not, the links will not be converted:
public string MakeMsgLink(string id, string msg, string cssClass)
{
string newClass;
if(msg == null || msg.Length == 0 || msg.StartsWith("<a "))
return msg;
StringBuilder sb = new StringBuilder(512);
sb.Append("<a ");
newClass = (cssClass == null) ?
this.MsgLinkCssClass : cssClass;
if(newClass != null && newClass.Length > 0)
{
sb.Append("class='");
sb.Append(newClass);
sb.Append("' ");
}
sb.Append("href='javascript:return false;' " +
"onclick='javascript: return BP_funSetFocus(\"");
sb.Append(id);
sb.Append("\", true);'>");
sb.Append(msg);
sb.Append("</a>");
return sb.ToString();
}
The MakeMsgLink
method will convert the passed text into a hyperlink that transfers focus to the control with the specified ID. The Set Focus script, described in part one of this series, controls setting the focus to the control. As such, the specified ID can be an exact match or the ending part of an ID (see part one for details). An optional CSS class name can be specified that will be applied to the hyperlink. If null
, it uses the one defined by the MsgLinkCssClass
property. By default, it is set to the value of the BasePage.MsgLinkCssName
constant which is currently set to the style name ErrorMsgLink. The class name should appear in the stylesheet associated with the application. As noted, a dummy href
is added to the link so that you can add a hover
style to the CSS class. For example, in my applications, the error messages display as normal text and show an underline as the mouse passes over them.
Although small, the PageUtils
class contains some very helpful features. The validation message link feature of BasePage
can also make the validation summary control more user friendly. Hopefully, you will find this class and the others in the library, or parts of them, as useful as I have.
- 04/02/2006
Changes in this release:
- Reworked the URL encoding regular expression in
PageUtils
so that it includes a few more protocols, includes all valid URL characters, handles URLs with parameters, and does not include special characters after the URL.
- Breaking change: Property and method names have been modified to conform to the .NET naming conventions with regard to casing (
PageUtils.HtmlEncode
, BasePage.MsgLinkCssClass
, and BasePage.MsgLinkCssName
).
- 11/26/2004
- Made some changes to the URL and UNC link encoding regular expressions to make them more accurate.
- 12/01/2003