在上一篇中,我们知道了HTTP属性与客户端缓存的关系,现在就可以着手用ASP.NET来控制这种缓存。需要注意的是,ASP.NET的Cache是用于服务器端缓存的,所以和我们正在讨论的事情完全无关,我们在这里要讨论的是如何通过HTTP属性控制客户端缓存。
页面缓存
在ASP.NET中,如果你需要添加HTTP属性,可以使用HttpResponse.AppendHeader方法,例如在Page的代码中直接执行Response.AppendHeader。HttpResponse.AddHeader方法是与之等效的,不过仅用于与ASP代码兼容,所以我的建议你最好不要使用。通过AppendHeader方法,你可以将上述Last-Modified属性和ETag属性写入返回中。
接着我们考虑如何从请求中读上述属性然后判断如何返回。我们可以使用HttpRequest.ServerVariables读取请求中的属性,然后和当前的值比较,如果比较结果表明内容无变化,我们就可以设置HttpResponse.StatusCode为304,然后返回空内容;如果比较结果表明内容变化了,那就还是按一般的方式完成整个返回。
这很麻烦,对吧?所以ASP.NET内置了HttpCachePolicy类,让我们可以直接控制有关属性,我们可以通过HttpResponse.Cache访问此类的实例,而如果在Page中我们可以直接通过Reponse.Cache访问它。这个类的使用方式在MSDN中有详细的描述,所以我就不再解释了。由于它的实现也依靠上述HTTP属性,所以使用AppendHeader控制上述属性时,就会破坏掉HttpCachePolicy中的设置(如果你设置了的话)。因此直接使用AppendHeader与通过HttpCachePolicy间接控制这两个方法中,同一时间最好仅用其中的一个,如果你需要灵活性就使用前者,如果你需要简单设置就是用后者。
资源缓存
ASP.NET内置了Cache和HttpCachePolicy,这让Page的缓存已经足够方便,所以让我们来看一看非Page该怎么缓存。事实上资源文件(例如js和css)的最大可能请求数量比Page要多得多,因为一个Page通常链接几个资源文件。
编译嵌入资源
我们先来看看编译控件是如何缓存资源的。系统自带的很多控件都是带有资源的,因为他们需要这些小图片、脚本或样式来确保它们的正常运行,这些资源编译时选择为嵌入到dll中,之后无论控件发布到哪都会附带有这些资源。这些嵌入到dll中的资源以特定的形式引用,在控件呈现为HTML代码时就成了WebResource.axd开头链接,例如:
<script
src="/WebResource.axd?d=7wVzVzBOs3_HEjhM5umRSQ2&t=632962899860156250"
type="text/javascript">
</script>
WebResource.axd注册为由AssemblyResourceLoader处理,这个IHttpHandler专门负责从dll中将资源文件提取出来,然后返回给客户端。
留意WebResource.axd后面的两个参数,d是资源的标示,它表明了当前请求的是哪个资源;t是该dll最后编译的时间戳,如果dll重新编译了t就会跟着改变,这就让浏览器知道这是一个新的URL,不应该再使用原来的缓存。
需要强调的是,这并非是一个具有兼容性的做法,它只能确保资源更新时缓存过期,但不能确保没更新的资源成功缓存。根据RFC2616,浏览器操作分为安全与不安全两类,GET和HEAD应该是安全的,因为除了获取信息它们不对外界造成任何影响;POST、PUT以及DELETE是不安全的,因为它们对外界造成影响,所以你刷新POST后的页面时浏览器会提示你是否确认再次提交数据。RFC2616中提到,对于安全操作除非服务器端显式声明过期,否则客户端有权直接取缓存来显示,因为无论客户端是从服务期端取还是从缓存取都应该是不对外界造成任何影响的,然而有一种情况除外——就是当URL中存在QueryString时。
当URL中存在QueryString时,这个请求被认为是可能对外界造成影响的,所以当客户端进行这个请求时必须通过服务器端完成,也就是不允许使用缓存。RFC2616如是说了,但并非每一个浏览器都如此做了。IE和Firefox违反RFC2616对有QueryString的URL进行缓存,而Opera和Safari则遵守此规矩每次重新获取内容。也就是说,ASP.NET的这种资源地址在Opera和Safari中是决不会被缓存的,例如你的ASP.NET应用在MasterPage使用了ASP.NET AJAX的ScriptManager,那么打开每个页面时有关的脚本文件都要从新下载。
非编译嵌入资源
如果我们当前在写一个ASP.NET网站,有些资源是直接以文件形式存在的,不是编译嵌入到dll中的,那么我们就没办法享受上述系统提供的便利了,但我们可以自己实现类似的机制,并避免上述某些浏览器不缓存资源的问题。详细的实现方式将在本系列文章的下一篇中讨论,如果你不想错过其中的精彩内容,请订阅Cat in dotNET。