65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 通用网页类库 - 第 4 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (11投票s)

2003年12月1日

CPOL

7分钟阅读

viewsIcon

152257

包含一些对 ASP.NET 应用程序有用的功能的实用工具类。

目录

引言

这是我开发的一系列 ASP.NET 应用程序类库文章中的第四篇。它包含一组通用的、可重用的页面类,可以直接在 Web 应用程序中使用,以提供一致的外观、感觉和功能集。也可以从它们派生新类来扩展其功能。这些功能都是相当模块化的,也可以提取并放入您自己的类中。有关系列文章的完整列表以及演示应用程序和类代码,请参见 第一部分 [^]。

本文描述了该类库中唯一一个非派生自 Page 的类 PageUtils,以及 BasePage 类中一些与之性质相似的其余方法。PageUtils 包含一套您可能在任何 ASP.NET 应用程序中有用的实用函数。每个功能在下面都有描述。该类本身是密封的,所有 public 属性和方法都是 static 的。因此,构造函数被声明为 private,因为没有必要实例化该类。

HTML 编码

第一个介绍的方法是 HtmlEncode,可以调用它来对对象进行编码,以便输出到 HTML 页面。它会将任何 HTML 特殊字符编码为字面量,而不是让浏览器解释它们。此外,它还会用其 HTML 等效项替换多个空格、制表符和换行符,从而保留指定文本的布局。可以通过 TabSize 属性更改扩展制表符的大小。将其设置为应替换制表符的非换行空格数。默认值为四。

如果对象为 null (Nothing)、结果为空字符串或单个空格,则返回一个非换行空格。结合上述行为,这对于显示包含 HTML 特殊字符、格式或 null 值(例如文本或 memo 数据类型的字段)的数据库字段非常有用。

作为一个额外的奖励,如果 encodeLinks 参数为 true,则尽可能使用 EncodeLinks 方法(见下文)将 URL、UNC 和电子邮件地址转换为超链接。如果为 false,则不会转换它们,并将作为普通文本呈现。如下所示,代码相当简单,几乎不需要额外的解释。

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 " ";
}

链接编码

介绍的第二个方法是 EncodeLinks。此方法由 HtmlEncode 调用,但也可以直接由您的代码调用。它接收传入的字符串,找到所有 URL、UNC 和电子邮件地址,并将它们转换为适用于在 HTML 页面中呈现的可点击超链接。对于 UNC 路径,它将包含到第一个空格字符为止的任何文本。如果路径包含空格,则可以将整个路径括在尖括号中(例如,<\\Server\Folder\Name With Spaces>),编码器会将尖括号之间的所有文本包含在超链接中。尖括号不会出现在编码的超链接中。

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;
}

正如您所见,该方法使用正则表达式来搜索和替换每个 URL、UNC 和电子邮件地址。使用的表达式应能捕获几乎所有这些类型的变体。正则表达式对象在首次使用时创建,并为后续调用保留,以节省一些时间。对于 URL 和 UNC,以下匹配评估器处理实际的替换工作。

// 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();
}

正则表达式匹配评估器就像一个回调。每次正则表达式找到匹配项时,它都会调用评估器。它的任务是获取找到的文本,根据需要对其进行修改,然后将其返回给正则表达式,以便用它来替换原始文本。在这两种情况下,匹配评估器都会添加锚点标记并确保链接格式正确。

将验证消息转换为超链接

在我的应用程序中,我开始倾向于使用验证摘要控件来包含页面生成的所有验证错误消息。它将它们都集中在一个位置,并且在可见时不会对表单控件的布局产生不利影响。缺点是,对于具有大量控件和验证条件的表单,有时很难将每条消息与相应的控件匹配,特别是当表单足够长以至于需要滚动才能找到它时。因此,我在 BasePage 类中添加了功能,可以自动将设置为显示在验证摘要控件中的所有验证控件错误消息转换为可点击的超链接,通过将焦点定位到有问题的字段,直接跳转到该字段。

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);
    }
}

在重写的 Render 方法的第一步调用 ConvertValMsgsToLinks。它会遍历页面的 Validators 集合。validator 控件必须可见,必须将其 ControlToValidate 属性设置为控件 ID,并且必须将其 Display 属性设置为 None,表示它将显示在验证摘要控件中。如果所有必需的条件都满足,则调用 MakeMsgLink 方法将错误消息转换为超链接。

请注意,由于这发生在渲染步骤中,因此对错误消息的更改不会保留。如果页面回发,错误消息将从视图状态恢复,并且将是其非超链接形式。当页面在回发时渲染时,只要消息仍然满足必要条件,它们就会再次转换为超链接。我选择这种方法是因为它对类的用户是透明的,不具侵入性,并且不会破坏任何期望消息为非超链接形式的代码。派生类可以重写此方法来扩展或抑制此行为。

注意:如果将上述方法提取到您自己的类中使用,请务必重写页面的 Render 方法并调用它。否则,链接将不会被转换。

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();
}

MakeMsgLink 方法会将传入的文本转换为超链接,该超链接会将焦点转移到指定 ID 的控件。Set Focus 脚本(在本系列 第一部分 中进行了描述)控制着将焦点设置到控件。因此,指定的 ID 可以是精确匹配,也可以是 ID 的结尾部分(有关详细信息,请参阅 第一部分)。可以选择指定一个 CSS 类名,该类名将应用于超链接。如果为 null,则使用 MsgLinkCssClass 属性定义的类名。默认情况下,它设置为 BasePage.MsgLinkCssName 常量的值,该常量当前设置为样式名称 ErrorMsgLink。类名应出现在与应用程序关联的样式表中。如前所述,链接会添加一个虚拟的 href,以便您可以为 CSS 类添加 hover 样式。例如,在我的应用程序中,错误消息显示为普通文本,并在鼠标悬停时显示下划线。

结论

PageUtils 类虽然小,但包含一些非常有用的功能。BasePage 的验证消息链接功能也可以使验证摘要控件更加用户友好。希望您能像我一样发现这个类以及库中的其他类(或它们的部分)很有用。

修订历史

  • 04/02/2006

    此版本中的更改

    • 重新编写了 PageUtils 中的 URL 编码正则表达式,使其包含更多协议,包括所有有效的 URL 字符,处理带参数的 URL,并且不包含 URL 之后的特殊字符。
    • 重大更改:属性和方法名称已修改,以符合 .NET 命名约定(关于大小写)(PageUtils.HtmlEncodeBasePage.MsgLinkCssClassBasePage.MsgLinkCssName)。
  • 11/26/2004
    • 对 URL 和 UNC 链接编码正则表达式进行了一些更改,使其更加准确。
  • 12/01/2003
    • 初始发布。
© . All rights reserved.