【问题标题】:ASP.NET - Parse / Query HTML Before Transmission and Insert CSS Class ReferencesASP.NET - 在传输之前解析/查询 HTML 并插入 CSS 类引用
【发布时间】:2012-06-13 19:19:27
【问题描述】:

作为一名网络开发人员,我觉得我的太多时间都花在了 CSS 上。我正在尝试提出一个解决方案,我可以编写 re-usable CSS 即类并在 HTML 中引用这些类,而无需在 ASPX 或 ASCX 文件等或代码隐藏文件中添加额外代码。我想要一个 intermediary 将 HTML 元素与 CSS 类链接起来。

我想要达到的目标:

  • 在传输前立即修改 HTML
  • 在 HTML 中选择元素
  • 基于其他地方定义的规则(例如,在与 当前正在处理的页面):
  • 向多个 HTML 元素添加 CSS 类引用
  • 将多个 CSS 类引用添加到单个 HTML 元素

我如何设想这项工作:

  1. 扩展生成最终 HTML 的 ASP.NET 函数
  2. 将所有 HTML 作为字符串获取
  3. 将字符串传递给具有查询(例如 XPATH)方法的对象的构造器
  4. 浏览全局规则列表,例如给孩子ul,首先是div,然后是class = "navigation"
  5. 浏览页面特定规则列表,例如给孩子ul,先是div,然后是class &= " home"
  6. 从对象获取已处理的 HTML,例如obj.ToString
  7. ASP.NET 使用已处理的 HTML 恢复页面生成

所以我需要知道的是:

  1. 在哪里/如何扩展 ASP.NET 页面生成功能(以获取页面的所有 HTML)
  2. 哪些类有元素/节点查询方法和属性访问权限

提前感谢您的帮助。

附:我正在开发 ASP.NET Web 表单网站,其中包含在 ISS 7 上运行的 VB.net 代码隐藏

【问题讨论】:

  • 我没有关于这个特定方法的答案,但如果你想编写可重用的 CSS,我建议学习一种 CSS 生成语言,如 SASS 或 Less。
  • 恕我直言,这种方法会导致严重的性能问题(实现最终的 HTML,而不是将其流式传输到标准响应输出流、重新解析、修改等)。无论如何,您知道 ASP.NET 设备过滤技术 (msdn.microsoft.com/en-us/library/ms178620.aspx) 吗?这可以帮助您实现目标。
  • 设备过滤看起来很有趣,但它不需要更改我在 ASPX 页面中的标记吗?我正在尝试弄清楚如何将其与我的 ASPX 页面分开并在“全局”范围的文件中进行代码隐藏。
  • @ChrisCannon - 是的,它基于标记。 ASP.NET 标记扩展通常基于一些初始标记。 (ps:当您在 SO 上为某人发表评论时,请在您的评论前加上“@nickname”,否则收件人不知道您发表了评论)

标签: asp.net xml-parsing webforms html-parsing csquery


【解决方案1】:

查看我的 CsQuery 项目:https://github.com/jamietre/csquery 或在 nuget 上作为“CsQuery”。

这是 jQuery 的 C# (.NET 4) 端口。在基本性能测试(包含在项目测试套件中)中,选择器比 HTML Agility Pack + Fizzler(HAP 的 css 选择器插件)快大约 100 倍;在典型的网站上实时操作输出流非常快。如果你是 amazon.com 什么的,当然是 YMMV。

我开发它的最初目的是从内容管理系统中操作 HTML。启动并运行它后,我发现使用 CSS 选择器和 jQuery API 比使用 Web 控件有趣得多,并开始将其用作服务器渲染页面的主要 HTML 操作工具,并将其构建为涵盖几乎所有的 CSS、jQuery 和浏览器 DOM。从那以后我再也没碰过网络控件。

要使用 CsQuery 拦截 Web 表单中的 HTML,您可以在页面代码隐藏中执行此操作:

using CsQuery;
using CsQuery.Web;

protected override void Render(HtmlTextWriter writer)
{

    var csqContext = WebForms.CreateFromRender(Page, base.Render, writer);

    // CQ object is like a jQuery object. The "Dom" property of the context
    // returned above represents the output of this page.

    CQ doc = csqContext.Dom;

    doc["li > a"].AddClass("foo");

    // write it
    csqContext.Render();
}

要在 ASP.NET MVC 中做同样的事情,请参阅 this blog post 描述。

GitHub 上有 CsQuery 的基本文档。除了输入和输出 HTML 之外,它的工作方式与 jQuery 非常相似。上面的WebForms 对象只是为了帮助您处理与HtmlTextWriter 对象和Render 方法的交互。通用用法很简单:

var doc = CQ.Create(htmlString);

// or (useful for scraping and testing)
var doc = CQ.CreateFromUrl(url);

// do stuff with doc, a CQ object that acts like a jQuery object

doc["table tr:first"].Append("<td>A new cell</td>");

此外,几乎整个浏览器 DOM 都可以使用您使用的相同方法获得 在浏览器中。索引器 [0] 返回选择集中的第一个元素,如 jquery;如果你习惯写 javascript 来操作 HTML 应该很熟悉。

// "Select" method is the same as the property indexer [] we used above.
// I go back and forth between them to emphasise their interchangeability.

var element = dom.Select("div > input[type=checkbox]:first-child")[0];
a.Checked=true;

当然,在 C# 中,您可以使用大量其他通用工具,例如 LINQ。或者:

var element = dom["div > input[type=checkbox]:first-child"].Single();

a.Checked=true; 

当您完成对文档的操作后,您可能想要获取 HTML:

string html = doc.Render();

仅此而已。 CQ 对象上有大量的方法,涵盖了所有 jQuery DOM 操作技术。还有一些处理 JSON 的实用方法,它广泛支持动态和匿名类型,以使传递数据结构(例如一组 CSS 类)尽可能简单——就像 jQuery。

一些更高级的东西

除非您熟悉 asp.net 的 http 工作流程的低级修补,否则我不建议您这样做。没有什么是可以撤消的,但如果您从未听说过 HttpHandler,就会有一个学习曲线。

如果您想完全跳过 WebForms 引擎,您可以创建一个自动解析 HTML 文件的IHttpHandler。这肯定会比覆盖在 ASPX 引擎上执行得更好——谁知道呢,甚至可能比使用 Web 控件进行类似数量的服务器端处理还要快。然后您可以register your handler using web.config 获取特定的扩展名(例如htm 和html)。

另一种自动拦截的方法是使用路由。您可以在 webforms 应用程序中毫无问题地使用 MVC 路由库,here's one description of how to do this. 然后您可以创建一个匹配您想要的任何模式的路由(同样,也许是 *.html)并将处理传递给自定义 IHttpHandler 或类.在这种情况下,您正在做所有事情:您需要查看路径,从文件系统加载文件,使用 CsQuery 解析它,然后流式传输响应。

当然,使用任何一种机制,您都需要一种方法来告诉您的项目要为每个页面运行什么代码。也就是说,仅仅因为您已经创建了一个漂亮的 HTML 解析器,那么您如何告诉它为该页面运行正确的“隐藏代码”?

MVC 只需找到一个名为“PageNameController.cs”的控制器并调用与参数名称匹配的方法即可。你可以为所欲为;例如你可以添加一个元素:

<script type="controller" src="MyPageController"></script>

您的通用处理程序代码可以查找这样的元素,然后使用反射来定位要调用的正确命名类和方法。这非常复杂,超出了这个答案的范围;但是,如果您正在寻找构建一个全新的框架或其他东西,这就是您要做的事情。

【讨论】:

  • 感谢您的回复,但我不得不说更换 WebForms 引擎似乎很疯狂!但是,疯子和天才之间有一条细线!我将在某个时候转向 MVC,但我的主要目标 ATM 是减少将 CSS 类添加到众多 ASPX 页面等所花费的时间。这就是 CSQuery 将发挥作用的地方,我计划如上所述覆盖 Render 方法,然后调用我自己的函数可以有效地进行所有解析,每个页面的 Render 方法将只是我的 CssHelper 类中的 AddCssClasses 函数的包装器。
  • 我对您的帖子有几个问题:(a) 您提到并非所有 DOM 都可用?只是好奇! (b) 你提到 CsQuery 比 HAP + Fizzler 快,但它肯定比单独的 HAP 快吗? (c) 请扩展“它广泛支持动态和匿名类型,以使传递数据结构(例如一组 CSS 类)尽可能简单”,因为我不确定一组 CSS 类如何成为数据类型?我想我明白你的意思我只是不是 100%
  • a) 并非所有 DOM 元素属性都已实现,在许多情况下是因为它在客户端模型(例如事件)中没有意义,在某些情况下是因为属性本​​质上与在这种情况下的一个属性(例如“href”),所以它不是那么重要。 b) 单独的 HAP 真的很慢。我最初使用 XML 选择器对我的 6MB 文档进行了复杂的后代选择器测试;花了几分钟。单独的 HAP 不以任何方式编入索引。 c) 有关“css(map)”,请参阅 jquery 文档:api.jquery.com/css 并参阅 CsQuery github 页面上的自述文件以将 CSS 道具作为匿名对象传递。
  • 哦..“替换 WebForms 引擎似乎很疯狂!”是的,我在这个大的遗留项目中被 WebForms 卡住了,并且已经设置了路由,所以我可以使用 REST 路径,所以当时并没有那么大的延伸:) 我能说什么我是一个黑客.你计划做的方式肯定是最适合你的目标的,我只是不想留下任何东西!
  • 还有一条评论:json vs. CSS - CsQuery 实际上会让你直接传入 JSON,例如doc["div &gt; span"].CssSet("{ \"height\": 10, \"width\": 10}"); 将在所有 div 的所有跨度第一个子项上添加值为 10 的 CSS 样式高度和宽度。您还可以使用对象,例如doc["div &gt; span"].CssSet(new { height=10, width=10}); 对匿名对象做同样的事情。有关详细信息,请参阅 github 上的自述文件。
【解决方案2】:

在发送页面之前拦截页面内容是相当简单的。不久前,我在一个动态压缩内容的项目上做了这个:http://optimizerprime.codeplex.com/(它很丑,但它完成了它的工作,你也许可以挽救一些代码)。无论如何,您要做的是:

1) 创建一个 Stream 对象,在调用 Flush 之前保存页面的内容。例如,我在压缩项目中使用了它:http://optimizerprime.codeplex.com/SourceControl/changeset/view/83171#1795869 就像我之前说的,它并不漂亮。但我的观点是,您需要创建自己的 Stream 类来执行您想要的操作(在这种情况下,为您提供页面的字符串输出,解析/修改字符串,然后将其输出给用户)。

2) 将页面的过滤器对象分配给它。 (Page.Response.Filter) 请注意,您需要尽早执行此操作,以便捕获所有内容。我使用在 PreRequestHandlerExecute 事件上运行的 HTTP 模块来完成此操作。但如果你做了这样的事情:

    protected override void OnPreInit(EventArgs e)
    {
        this.Response.Filter = new MyStream();
        base.OnPreInit(e);
    }

这也很有可能奏效。

3) 您应该能够使用 Html Agility Pack 之类的东西来解析 HTML 并从那里修改它。

对我来说,这似乎是最简单的方法。

【讨论】:

  • 我更倾向于覆盖页面的渲染方法,然后将渲染方法的参数传递给我自己的函数,该函数在我自己的类中 - 在其他地方,它通过 HTML 解析器进行处理(然而即将被决定)。我有点反对实现我自己的流类,因为我需要实现所有方法,而且这种方式似乎更复杂。
  • 实际上,您真正需要实现的唯一方法是 write 和 flush。在大多数情况下,其他所有内容都可以忽略不计,因为您既不是从流中读取也不是在寻找(嗯,需要实现 3 个属性 [CanSeek、CanRead、CanWrite],这大约需要 20 秒)。
  • 好的,感谢您的提示,但我认为实现我自己的流类对于我正在尝试做的事情来说太过分了!
猜你喜欢
  • 1970-01-01
  • 2019-09-22
  • 2021-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 2014-03-14
相关资源
最近更新 更多