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

缓存浏览器中的 WCF JavaScript 代理

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2012 年 4 月 4 日

CPOL

3分钟阅读

viewsIcon

43126

downloadIcon

249

WCF JavaScript 代理 (Service.svc/js) 从未被缓存。它们在每次页面浏览时都会生成并下载,从而增加了页面下载时间和服务器 CPU 占用率。这是一个 HttpModule,用于在浏览器上缓存 WCF JavaScript 代理,并在未更改时使用 HTTP 304 响应。

下载 WcfJavascriptProxyCaching.zip 

介绍 

当您使用 JavaScript 中的 WCF 服务时,您必须通过访问 Service.svc/js 来生成 JavaScript 代理。如果您有五个 WCF 服务,这意味着要下载五个 JavaScript 文件。由于浏览器会同步下载 JavaScript 文件,一个接一个,这会增加页面加载的延迟并降低页面渲染性能。此外,从每个页面下载相同的 WCF 服务代理,因为生成的 JavaScript 文件未在浏览器上缓存。这里有一个解决方案,可以确保生成的 JavaScript 代理在浏览器上被缓存,并且当服务被访问时,如果 Service.svc 文件未更改,它将使用 HTTP 304 响应。  

问题 

以下是使用两个 WCF 服务的页面 Fiddler 跟踪记录。

您可以看到有两个 /js 访问,它们是顺序的。即使使用相同的浏览器会话,每次访问同一页面也会导致对 /js 进行这两次访问。第二次浏览同一页面时

 

您可以看到所有其他内容都被缓存了,除了 WCF JavaScript 代理。它们从未被缓存,因为 WCF JavaScript 代理生成器不会生成必要的缓存标头来在浏览器上缓存这些文件。 

解决方案 

这是一个用于 IIS 和 IIS Express 的 HttpModule,它将拦截对 WCF 服务代理的调用。它首先检查自浏览器上的缓存版本以来服务是否已更改。如果未更改,则它将返回 HTTP 304,并且不会经过服务代理生成过程。因此,它节省了服务器上的一些 CPU 资源。但是,如果这是第一次请求,并且浏览器上没有缓存副本,它将提供代理,并发出适当的缓存标头,以便在浏览器上缓存响应。

为了使服务被缓存,首先您需要更改对服务的引用方式。

<asp:ScriptManager runat="server">
  <Services>
    <asp:ServiceReference Path="~/(v2)/Service1.svc" />
  </Services>
</asp:ScriptManager>

请注意服务引用中的 (v2)。这是版本号。每当您对服务进行一些更改并希望浏览器下载最新副本时,您都会更改版本。除非此版本在 URL 上,否则服务代理不会被缓存。这是一种安全机制。它防止 JavaScript 代理在浏览器上被缓存,并且您无法更新它们,除非您更改服务名称。

HttpModule 首先拦截 BeginRequest 以查看请求的 URL 是否为 WCF 服务代理,以及它是否已版本化。如果是,则它检查浏览器上缓存的副本是否已过时。如果 .svc 文件在此期间已更改,则缓存的副本已过时。如果没有,则它返回 HTTP 304 并结束请求。

public sealed class JavascriptProxyCacheModule : IHttpModule
{
  private HttpApplication app;
      
  public void Init(HttpApplication context)
  {
    app = context;
    app.EndRequest += new EventHandler(app_EndRequest);
    app.BeginRequest += new EventHandler(app_BeginRequest);
  }

  void app_BeginRequest(object sender, EventArgs e)
  {
    var path = app.Context.Request.Path.ToLower().Replace('\\', '/');
    if (IsWcfJavacsriptProxy(path))
    {
      // versioning is used by using (xxx) in the path of the script
      // reference. for ex, /(v2)/Service.svc/js
      // In this case, remove the (v2)/ 
      int pos = path.IndexOf('(');
      if (pos > 0)
      {
        int endPos = path.IndexOf(')');
        path = path.Substring(0, pos) + path.Substring(endPos + 2);
        app.Context.RewritePath(path);

        string ifModifiedSince = app.Context.Request.Headers["If-Modified-Since"];
        if (!string.IsNullOrEmpty(ifModifiedSince))
        {
          string filePath = app.Context.Request.PhysicalPath;
          DateTime fileLastModifyDateTime = File.GetLastWriteTime(filePath);
          DateTime browserLastModifyDateTime;
          if (DateTime.TryParse(ifModifiedSince, out browserLastModifyDateTime))
          {
            if ((browserLastModifyDateTime - fileLastModifyDateTime).Seconds > 5)
            {
              app.Context.Response.StatusCode = 304;
              app.Context.Response.End();
            }
          }
        }
      }        
    }
  }

它还钩住了 EndRequest,以确保当 WCF 服务代理被传递到浏览器时,发送了适当的缓存标头,以便浏览器将 JavaScript 缓存一段时间。可以随意更改缓存过期策略。

private void app_EndRequest(object sender, EventArgs e)
{
  var path = app.Context.Request.Path.ToLower().Replace('\\', '/');
  
  if (app.Context.Response.StatusCode == 200
    || app.Context.Response.StatusCode == 202)
  {
    if (IsWcfJavacsriptProxy(path) && IsVersioned())
    {
      var lastModified = DateTime.UtcNow;
      // Expire by tomorrow 7 AM always.
      var expires = lastModified.AddDays(1).Subtract(lastModified.TimeOfDay).AddHours(7);
      HttpCachePolicy cache = app.Context.Response.Cache;
      cache.SetLastModified(lastModified);
      cache.SetExpires(expires);
      cache.SetCacheability(HttpCacheability.Public);
    }
  }      

}

要使用它,您需要在 web.config 中添加此 httpmodule

    <httpModules>
      <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="JavascriptProxyCacheModule" type="WcfService1.JavascriptProxyCacheModule"/>
    </httpModules>

此解决方案仅适用于 IIS 和 IIS Express。 它不适用于 ASP.NET 开发服务器,因为开发服务器不允许在服务 URL 上使用 (v2)。  

© . All rights reserved.