缓存浏览器中的 WCF JavaScript 代理





5.00/5 (4投票s)
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)。