在Web应用程序中,WCF(Windows Communication Foundation)服务是一种常用的服务交互方式。然而,当使用WCF服务时,浏览器需要下载对应的JavaScript代理文件,这可能会导致页面加载延迟和渲染性能下降。这是因为浏览器会同步地、一个接一个地下载这些JavaScript文件。此外,由于生成的JavaScript代理文件没有在浏览器上进行缓存,所以每次访问相同页面时,都需要重新下载这些文件。
通过Fiddler工具追踪页面使用两个WCF服务的情况,可以发现存在两个对/js的连续请求。即使在相同的浏览器会话中,每次访问相同页面时,都会产生这两个请求。当第二次浏览同一页面时,除了WCF JavaScript代理之外,其他所有内容都被缓存了。由于WCF JavaScript代理生成器没有产生必要的缓存头信息,因此这些代理文件不会被浏览器缓存。
为了确保生成的JavaScript代理在浏览器上被缓存,并且当服务被访问时,如果Service.svc文件没有变化,则返回HTTP 304响应,可以采用以下解决方案。
实现一个IIS和IIS Express的HttpModule,该模块将拦截对WCF服务代理的调用。它首先检查自浏览器上缓存的版本以来服务是否已更改。如果没有更改,则返回HTTP 304,并且不会执行服务代理生成过程。这样不仅节省了服务器上的CPU资源,而且如果请求是第一次并且浏览器上没有缓存的副本,它将提供代理并发出适当的缓存头信息,以便在浏览器上缓存响应。
为了使服务被缓存,首先需要改变引用服务的方式。在ASP.NET中,可以通过以下方式添加服务引用:
<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))
{
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();
}
}
}
}
}
}
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;
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);
}
}
}
}
要使用这个HttpModule,需要在web.config文件中添加以下配置:
<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>