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

在 ASP.NET Web 控件中嵌入和内联资源

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (10投票s)

2011年6月27日

Ms-PL

11分钟阅读

viewsIcon

58948

downloadIcon

1007

如何有效地将资源嵌入到服务器控件中?如何使用 Web 资源、内容分发网络或图像内联?本文将为您解答。

引言

Web 组件(例如 ASP.NET Web 控件)通常需要包含或访问二进制数据,例如图像、样式表和 JavaScript。当然,您可以将它们与您的程序集松散地打包在一起,并强制开发人员将它们复制到您的组件期望它们所在的位置。但这会带来部署的噩梦,有时甚至是不可能的。

ASP.NET Web 资源应运而生,但它们也有其自身的代价和缺点。在本文中,我将介绍如何使用 Web 资源以及如何通过数据 URI 和内容分发网络最大限度地减少缺点。

在 ASP.NET 中使用 Web 资源

Web 资源机制允许您将任何数据(例如图像、CSS 和 JavaScript 文件)编译到您的程序集中。您可以使用 .NET 框架的一部分的特殊处理程序请求嵌入式资源。

此功能的主要优点是您需要部署的文件数量更少。典型的示例是图标库。您可以轻松获得一些库,例如 Silk Icon Set (http://www.famfamfam.com/lab/icons/silk/),但之后您需要维护和随身携带大量相当小但数量众多的图标。生活中的有些事情,除非亲身经历,否则永远无法完全体会或理解。在匆忙中通过不稳定的连接将完整的 1000 个 Silk 图标集复制到 FTP 服务器就是其中之一。

这个过程实际上非常简单,但容易出错、打字错误和误解,这些错误、打字错误和误解不会表现为错误消息或异常。它只是不起作用,您也不知道为什么。

本文提供了创建带有嵌入式资源的简单 Web 控件库的分步指南。

准备文件

首先,为您要包含在程序集中的文件创建一个特殊文件夹。它通常称为 *Resources*,位于项目的根目录中,但您可以在您的结构中的任何位置命名和放置它。将您的文件放在那里。当包含多个文件时,最简单的方法是将它们从资源管理器窗口拖到 Visual Studio 中的解决方案资源管理器中。

然后您需要将所有这些文件标记为 *Embedded Resources*。选择所有文件(您可以选择多个文件),然后在“属性”窗口中,将“生成操作”设置为 *Embedded Resource*(而不仅仅是 *Resource*)。

注册 Web 资源

下一步是将资源注册为 Web 资源。您可以在任何代码文件(C# 或 VB.NET)中使用 `WebResource` 属性完成此操作。您将属性放置在哪里并不重要,但我通常在嵌入式文件所在的文件夹中创建一个名为 *\_ResourceRegistration.cs* 的文件(下划线确保文件在按字母顺序排序时位于列表顶部)。

以下是注册单个图像的示例 *\_ResourceRegistration.cs* 文件

using System.Web.UI;

[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
                       "image/png")]

属性定义的第一个参数是资源的逻辑名称。逻辑名称是资源注册中最麻烦的部分,因为它必须与您的项目设置和文件名完全匹配。逻辑名称区分大小写,即使它包含的文件名通常不区分大小写。它的定义中的问题不会导致任何编译或运行时错误,它只是不起作用。

逻辑名称由三个部分组成,它们通过点连接和分隔。第一部分是项目的根命名空间。根命名空间在项目属性(“应用程序”选项卡)中设置,默认情况下与项目名称匹配。在上面的示例中,根命名空间是 `Altairis.ResourceDemo.Controls`。

第二部分是项目文件夹结构中的路径。在我们的例子中,文件放置在项目根目录中的 *Resources* 文件夹中;因此名称的一部分只是 *Resources*。在更复杂的结构中,点用作路径分隔符。第三个也是最后一个部分是文件名,包括扩展名。在上面的示例中,它是 *FlagBlue.png*。

第二个参数是内容的 MIME 类型。当使用 *WebResource.axd* 处理程序请求时,类型作为 Content-Type HTTP 标头发送,并且可能从 Web 浏览器角度来看很重要。最常见的 MIME 类型是用于图像格式的 *image/png*、*image/gif* 和 *image/jpeg*,用于 JavaScript 的 *text/javascript*,以及用于样式表的 *text/css*。

获取资源 URL

最后一步是获取最终资源 URL,您可以在其中访问嵌入式数据。这可以通过调用方法 `Page.ClientScript.GetWebResourceUrl` 来完成。该方法有两个参数:第一个是资源相关的类型,通常是使用它的控件。第二个是资源的逻辑名称。您可以通过调用获取上述资源的路径

var imageLink = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
                "Altairis.ResourceDemo.Controls.Resources.FlagBlue.png");

生成的路径指向 *~/WebResource.axd* 处理程序,看起来类似于以下内容

/WebResource.axd?d=dseRjrCHl7PvN70cMzoiaJKEsWjA2FTOp8sEou4qLQKJBO_kc1b3cnhEI6E-eWw
poXM-z4_GG5ApZbQR4SIsEOcpPyodXAeRk7ZKg-YQLxdLqIcBtUWb5mRQ-82wT8Pe2MxOiYZOyAN3dUvEj
bsvhqPfDjQdtgCF4HtTkdsrUF9XBmnkjvja_B1l9bG0GZce0&t=634317785760223219

长 Base-64 编码的查询字符串参数是资源的加密逻辑名称及其所在的程序集。

内容分发网络 (CDN)

上述机制在 ASP.NET Web 窗体基础结构本身中使用。例如,验证控件(例如 `RequiredFieldValidator`、`RegularExpressionValidator` 等)使用的辅助 JavaScript 文件就是以这种方式加载的。

随着 ASP.NET 4.0 的发布,Microsoft 推出了自己的内容分发网络 (CDN)。ASP.NET 基础结构所需的所有文件,以及 jQuery 和一些 jQuery 插件,都托管在 ajax.aspnetcdn.com。CDN 文件的完整列表可以在以下地址找到:http://www.asp.net/ajaxlibrary/cdn.ashx

使用内容分发网络的主要原因是性能。CDN 由放置在世界不同地区的许多服务器组成,用户的请求被路由到最近的边缘缓存服务器,该服务器可能比您自己的服务器更快到达。此外,CDN 资源由浏览器缓存。当不同的 ASP.NET 应用程序使用相同的支持脚本时,它们不会多次加载(例如当它们从每个网站单独请求时),而只会从共享位置加载一次。

要为核心服务器控件启用 ASP.NET CDN,请将 `ScriptManager` 控件放置在您的页面中,并将其 `EnableCdn` 属性设置为 `true`。如果您不使用 Microsoft AJAX 框架,您也可以禁用其自动引用

<asp:scriptmanager runat="server" enablecdn="true" ajaxframeworkmode="Disabled" />

将 CDN 与 Web 资源一起使用

例如,如果您是组件供应商,您可能希望使用类似的技术来分发您的资源。您可能希望运行自己的 CDN 并从那里引用嵌入式资源。

但是,将 CDN 路径硬编码到您的组件中并强制用户使用它们并不是一个好主意。在某些情况下,例如内网应用程序(其中 Internet 访问受限)或离线开发,CDN 不受欢迎。因此,建议像 Microsoft 一样做:允许开发人员使用 `ScriptManager` 控件打开和关闭 CDN。

注册资源时,您可以使用 `WebResource` 属性的额外命名参数 `CdnPath`,其中包含资源文件在您的 CDN 中所在的完整路径。当 `EnableCdn` 设置为 `true` 时,将发出 CDN 链接而不是调用 *WebResource.axd*。

上述示例可以修改如下

using System.Web.UI;

[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
            "image/png", 
            CdnPath = "http://yourcdn/Samples/EmbeddingResources/FlagBlue.png")]

ASP.NET 不会帮助您构建 CDN 本身,它只会链接到它。要构建 CDN,您可以使用各种方法,从 CloudFlare (www.cloudflare.com) 等简单免费的解决方案开始,到 Akamai 等大型提供商的付费服务,再到在全球部署您自己的服务器集群,这取决于您。

将 SSL 与 CDN 一起使用

当您的应用程序使用加密连接(HTTPS 或 SSL/TLS)时,CDN 的使用可能会出现问题。从安全页面引用不安全内容存在安全风险,浏览器会向用户显示各种烦人的警告。

在默认设置中,CDN 仅在通过非安全连接请求页面时使用。如果采用 SSL,链接将回退到 *WebResource.axd* 方法。

如果您的 CDN 也提供 HTTPS 连接,您可以通过添加一个名为 `CdnSupportsSecureConnection` 的额外参数并将其设置为 `true` 来指示它。注册将类似于

[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
            "image/png", 
            CdnPath = "http://yourcdn/Samples/EmbeddingResources/FlagBlue.png",
            CdnSupportsSecureConnection = true)]

当此参数设置为 `true` 并且页面通过 HTTPS 请求时,`CdnPath` 也会修改为使用 HTTPS。在上面的示例中,链接将以 *https://yourcdn/...* 开头。

数据 URI 方案

有时您可以通过使用数据 URI 完全避免使用 Web 资源。通常,URI 包含标识对象(统一资源标识符)的 HTTP 地址,并且通常还定位它,给出对象可以位于的地址。

在数据 URI 的情况下,对象本身包含在 URI 中。这种方法最常用于小图像,例如图标,其中二进制图像数据以 Base64 编码的形式嵌入在 URI 本身中。请参阅以下示例(为便于阅读而缩短)

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB………QCAYAAAAf8/9h=" />

数据 URI 的结构在 RFC2397 中正式定义,但通常采用上面所示的形式。它以“`data:`”方案前缀开头,后跟数据的内容类型(这里是“*image/png*”,如上所述),分号,编码类型(“*base64*”),冒号,以及 Base64 编码的二进制数据。

使用数据 URI 的主要优点是限制了 HTTP 请求的数量。如果您的页面使用大量小图像,在浏览器中显示它可能涉及相当多的 HTTP 请求。尽管数据本身的量不大,但开销却相当大。通过将数据嵌入到页面本身,您可以限制请求的数量。除了降低事务成本外,限制请求数量还降低了 Web 服务器的负载。

主要缺点是 Base64 编码的数据比其二进制形式大约 1/3。此外,当以上面概述的简单方式使用时,如果在一个页面中重复使用,它们每次都必须重复。

将样式表与数据 URI 方案一起使用

第二个缺点,重复出现特定图像的问题,可以通过不直接使用“`src`”属性引用数据,而是使用 CSS 类并将图像数据包含在样式表文件中来轻松解决。

要显示红色旗帜图标,我们可以在页面本身中定义以下 HTML

<span class="redflag"></span>

然后我们在页面样式表中定义以下标记(同样,为便于阅读,Base64 数据已修剪)

.redflag {
    display: inline-block;
    width: 16px;
    height: 16px;
    background-position: center center;
    background-repeat: no-repeat;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAA…);
}

完整示例

本文中显示的所有方法都结合在您可以下载的示例应用程序中。源代码包含两个项目。*Altairis.ResourceDemo.Controls* 是一个类库,包含一个 Web 控件和一堆资源。第二个项目是使用控件库的示例网站。

我们定义了一个名为 `FlagIcon` 的 Web 控件。该控件显示本文中已提及的 Silk 图标集中包含的七种彩色旗帜图像之一。该控件有两个有趣的属性

  • `FlagColor` 是一个枚举,包含所有可用的旗帜颜色,并定义将显示哪个图像。
  • `LinkMode` 控制图像的显示方式。当设置为 `WebResource` 时,使用嵌入式资源或 CDN。当设置为 `DataUri` 时,图片作为数据 URI 内联到 HTML 中。第三个可能的值是 `StyleSheet`,它结合了这些方法。

结合 Web 资源和数据 URI

最后一种方法结合了此处提到的技术,并试图最大限度地增加收益并最大限度地减少损失。

核心是一个包含所有七个内联图像的样式表。该样式表作为 Web 资源编译到组件库中,并可通过 Altairis 内容分发网络获取。

主要挑战是在页面中引用样式表并且只引用一次,即使页面包含多个图标。这就是我开发的 `EnsureStyleSheetRegistration` 方法的用武之地

private void EnsureStyleSheetRegistration() {
    // We need <head runat="server"> for this code to work
    if (this.Page.Header == null)
        throw new NotSupportedException(
        "No <head runat="\""server\"> control found in page.");

    // Get the stylesheet resource URL
    var styleSheetUrl = this.Page.ClientScript.GetWebResourceUrl(
                        this.GetType(), STYLE_RESOURCE_NAME);

    // Check if this stylesheer is already registered
    var alreadyRegistered = this.Page.Header.Controls.OfType<HtmlLink>().Any(
                            x => x.Href.Equals(styleSheetUrl));
    if (alreadyRegistered) return; // no work here

    // If not, register it
    var link = new HtmlLink();
    link.Attributes["rel"] = "stylesheet";
    link.Attributes["type"] = "text/css";
    link.Attributes["href"] = styleSheetUrl;
    this.Page.Header.Controls.Add(link);
}

结论

ASP.NET 提供了一种将图像、样式表或 JavaScript 等资源嵌入到程序集中并通过 *WebResource.axd* 处理程序读取它们的出色机制。对于小图像,使用数据 URI 内联它们可能是一个更好的选择。内联的许多缺点可以通过结合上述方法并在样式表中定义内联图像来解决。

另一种替代方法,本文中未讨论,是使用图像精灵。如果您想在 ASP.NET 中探索这个想法,CodePlex 上的 ASP.NET Futures 中有一个 Sprite and Image Optimization Framework 项目:http://aspnet.codeplex.com/releases/view/50869

© . All rights reserved.