【问题标题】: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 生成语言,如 SASSLess
  • 恕我直言,这种方法会导致严重的性能问题(实现最终的 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 获取特定的扩展名(例如htmhtml)。

另一种自动拦截的方法是使用路由。您可以在 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
相关资源
最近更新 更多