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

完全控制您的 Webforms 输出

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2009年7月7日

CC (ASA 2.5)

4分钟阅读

viewsIcon

14600

完全控制您的 Webforms 输出

我非常喜欢正则表达式……非常。我知道我不是专家,但即使是业余爱好者,只需几个正则表达式,也能做出一些令人惊叹的事情。

WebForms 的输出一直有点……混乱。您的页面通常充斥着大量过长的 ID 和名称,用于实际上不需要的内容(例如,我曾在一个供应商程序中看到一个很棒的 ID: accounts_overview_body_quickAction_quickActionTitleLabel,仅用于一个 span 元素)。

您还必须接受 ViewState,它很快就会失控。您可以禁用它,但无论如何它仍然会渲染一些内容。可能不多,但当您不需要时,您宁愿它消失。

WebForms 的另一个令人讨厌之处是您无法在页面级别的表单内放置其他表单。有些浏览器不介意,但其他浏览器根本不起作用。如果您不需要 WebControls 做任何事情(例如保持其状态),那么能够完全删除它们会很好。

掌控您的页面

如我一开始提到的,通过重写页面的 Render 事件,您可以在输出发送给用户之前对其进行完全访问。我之前曾写过一些关于此主题的代码,当时我谈论的是最小化您的内容。如果您读过那篇文章,您就会对下一步会发生什么有一个大致了解。

第一步是实际渲染我们的内容,以便我们可以对其进行操作。不深入细节,我们将把内容写入我们自己的本地 HtmlTextWriter,以便在发送给实际 HtmlTextWriter 之前对其进行修改。这并不难……

protected override void Render(HtmlTextWriter writer) {

    //render our output
    StringWriter output = new StringWriter();
    HtmlTextWriter html = new HtmlTextWriter(output);
    base.Render(html);

    //get the rendered string
    string rendered = output.ToString();

    //Make our changes
    //... discussed shortly

    //cleanup our writers
    html.Dispose();
    output.Dispose();

    //then write it
    writer.Write(rendered);
}

很酷,对吧?我们现在有了 string,可以开始进行更改了。

删除 ViewState

有些页面确实根本不需要 ViewState。也许是产品信息,也许是几张照片,但页面上没有任何用户需要回发的控件。编写一个正则表达式来删除它并不难。

rendered = Regex.Replace(
    rendered,
    "<input.*name=\"__VIEWSTATE\"[^>]*>",
    string.Empty,
    RegexOptions.IgnoreCase
    );

这段简短的代码将完全从页面中删除 ViewState。您可以通过将表达式更改为 "<input.*name=\"__\\w+\"[^>]*>" 来进一步修改它,以删除其他内容(例如 __EVENTSTATE)。

移除页面表单

正如我在文章前面所说,如果您想在页面上放置其他表单,它们必须在页面表单之外。并非所有浏览器都支持嵌套表单。

使用 WebForms,您可以拥有一个页面表单,在其中使用所有控件,然后在发送给客户端之前删除标记。这样,您就可以使用 WebControls 和嵌套表单。

这个过程自然会稍微复杂一些。

//find all the forms and closed elements
MatchCollection matches = Regex.Matches(
    rendered,
    "</?form[^>]*>",
    RegexOptions.IgnoreCase
    );

//create the expression to match for the ID
//master pages rename the form to 'aspnetForm' -- weird
Regex expectedId = new Regex(
    string.Format("id=\"?({0}|aspnetForm)\"?", this.Form.ID),
    RegexOptions.IgnoreCase
    );

//expression to check for a close of a form
Regex closeForm = new Regex(
    @"</\s?form\s?>",
    RegexOptions.IgnoreCase
    );

//loop through and remove the page form
Match open = null;
Match close = null;
int depth = 0;

//we're checking for the open form THEN checking for the
//matching close tag (checking for nesting)
for (int i = 0; i < matches.Count; i++) {
    Match match = matches[i];

    //check if this is the page form
    if (expectedId.IsMatch(match.Value) && !(open is Match)) {
        open = match;
    }

    //if we aren't tracking yet, just continue
    if (!(open is Match)) { continue; }

    //change the direction of the nesting
    depth += closeForm.IsMatch(match.Value) ? -1 : 1;

    //if the nesting is back to zero we can assume this
    //is the correct tag
    if (depth == 0) {
        close = match;
        break;
    }
}

//remove the tags - make sure to start with the close tag
//since this will affect the index of the Matches
if (open is Match && close is Match) {
    rendered = rendered.Remove(close.Index, close.Length);
    rendered = rendered.Remove(open.Index, open.Length);
}

有了这段代码,我们就可以从页面中移除页面表单,但保留所有其他表单,以便它们可以按照自己的方式处理回发。

值得注意的是,与删除 ViewState 一样,依赖这些功能的postbacks 和 WebControls 很可能将不再起作用(或者至少不像预期那样)。

缩短您的 ID

ASP.NET 4.0 将提供一些有用的功能,以帮助开发人员将有意义的 ID 推送到客户端。目前,ID 是通过父级、父级的父级等的 ID 生成的。这对于避免命名冲突非常有用——但如果您试图做任何客户端操作,那就太糟糕了。不仅如此,有时您最终会在您无意操作的元素上设置 ID,这只是浪费空间。

再次,只需一点正则表达式的魔力,我们就可以更改页面上的 ID。

//lastly, drop any unwanted IDs
MatchCollection extraIds = Regex.Matches(
    rendered,
    @"<[^>]*actualId=""(?<id>[^""]*)""[^>]*>",
    RegexOptions.IgnoreCase
    );

//loop backwards to avoid affecting indexes on the matches
for (int i = extraIds.Count; i-- > 0; ) {
    Match extra = extraIds[i];

    //drop the unwanted parts
    string newElement = extra.Value;
    string newID = extra.Groups["id"].Value;

    //lastly, remove the actual ID field
    newElement = Regex.Replace(
        newElement,
        @"actualId=""[^""]*"" ?",
        string.Empty,
        RegexOptions.IgnoreCase
        );

    //if the ID is blank, just remove it
    newElement = Regex.Replace(newElement, @"(id|name)=""[^""]*"" ?", (str) => {
        if (string.IsNullOrEmpty(newID)) { return string.Empty; }
        return string.Concat(
            str.Value.StartsWith("id", StringComparison.OrdinalIgnoreCase) ? "id" : "name",
            "=\"",
            newID,
            "\" "
            );
    });

    //finally, replace the new string
    rendered = rendered.Remove(extra.Index, extra.Length);
    rendered = rendered.Insert(extra.Index, newElement);
}

显然,这至少可以说是自定义解决方案。这里的想法是,我们现在可以将属性“actualId”添加到我们的控件上,idname 属性将被更新以反映该值。此外,我们的 actualId 属性将完全从元素中删除。

您可以进一步调整代码,以获取所有未以某种方式标记的 ID,以保留其 ID。

绝对的权力导致腐败

深入研究您的渲染输出并更改发送给客户端的内容很有趣。您有很多权力可以完全改变您的内容的外观。但要小心——您更改的内容越多,ASP.NET 的工作方式就越有可能出错。

如果您想出一些很酷的使用方法,请告诉我!

注意:正如下面的评论者所指出的,这不适用于 UpdatePanels — 至少需要更多工作。UpdatePanels 的内容由管道分隔,并包含所有返回 ID 的内容的字符计数(我有一段时间没用过它们了,所以这是凭记忆)。在不破坏任何东西的情况下进行更改会相当棘手(但并非不可能!!)。

© . All rights reserved.