|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Table of contents
IntroductionThis 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, HTML encodingThe first method presented is If the object is As an added bonus, if the public static string HtmlEncode(Object objText, bool encodeLinks)
{
StringBuilder sb;
string text;
if(objText != null)
{
text = objText.ToString();
if(text.Length != 0)
{
// Create tab expansion string if not done already
if(expandTabs == null)
expandTabs = new String(' ',
PageUtils.TabSize).Replace(" ", " ");
// Encode the string
sb = new StringBuilder(
HttpUtility.HtmlEncode(text), 256);
sb.Replace(" ", " "); // Two spaces
sb.Replace("\t", expandTabs);
sb.Replace("\r", "");
sb.Replace("\n", "<br>");
text = sb.ToString();
if(text.Length > 1)
{
if(!encodeLinks)
return text;
// Try to convert URLs, UNCs, and e-mail
// addresses to links.
return PageUtils.EncodeLinks(text);
}
if(text.Length == 1 && text[0] != ' ')
return text;
}
}
return " ";
}
Link encodingThe second method presented is public static string EncodeLinks(string text)
{
// We'll create these on first use and keep them around
// for subsequent calls to save resources.
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);
}
// Do the replacements
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: // Replace a URL with a link to the URL. This checks for a
// missing protocol and adds it if necessary.
private static string OnUrlMatch(Match match)
{
StringBuilder sb = new StringBuilder("<a href='", 256);
string url = match.Value;
// Use default HTTP protocol if one wasn't specified
if(url.IndexOf("://") == -1)
sb.Append("http://");
sb.Append(url);
sb.Append("' target='_BLANK'>");
sb.Append(url);
sb.Append("</a>");
return sb.ToString();
}
// Replace a UNC with a link to the UNC. This strips off any
// containing brackets (plain or encoded) and flips the slashes.
private static string OnUncMatch(Match match)
{
StringBuilder sb = new StringBuilder("<a href='file:", 256);
string unc = match.Value;
// Strip brackets if found. If it has encoded brackets,
// strip them too.
if(unc[0] == '<')
unc = unc.Substring(1, unc.Length - 2);
else
if(unc.StartsWith("<"))
unc = unc.Substring(4, unc.Length - 8);
// Move trailing special characters outside the link
Match m = reTSUNC.Match(unc);
if(m.Success == true)
unc = reTSUNC.Replace(unc, "");
sb.Append(unc);
sb.Append("' target='_BLANK'>");
// Replace backslashes with forward slashes
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. Converting validation messages to hyperlinksIn 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 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 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 public string MakeMsgLink(string id, string msg, string cssClass)
{
string newClass;
// Don't bother if it's null, empty, or already in the form
// of a link.
if(msg == null || msg.Length == 0 || msg.StartsWith("<a "))
return msg;
StringBuilder sb = new StringBuilder(512);
// Add the anchor tag and the optional CSS class
sb.Append("<a ");
newClass = (cssClass == null) ?
this.MsgLinkCssClass : cssClass;
if(newClass != null && newClass.Length > 0)
{
sb.Append("class='");
sb.Append(newClass);
sb.Append("' ");
}
// An HREF is included that does nothing so that we can use
// the hover style to do stuff like underline the link when
// the mouse is over it. OnClick performs the action and
// returns false so that we don't trigger IE's
// OnBeforeUnload event which may be tied to data change
// checking code.
// NOTE: OnPreRender registers the script containing the
// function. Tell the function to use the "Find Control"
// method to locate the ID. That way, it works for controls
// embedded in data grids.
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 ConclusionAlthough small, the Revision history
|
||||||||||||||||||||||