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

ASP.NET AJAX 脚本方法访问的客户端缓存

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (4投票s)

2007 年 6 月 29 日

CPOL

7分钟阅读

viewsIcon

64402

作为 ASP.NET AJAX 的关键功能之一,在构建使用 ASP.NET 的 AJAX 应用程序时,从客户端调用脚本方法将非常常用。虽然存在服务器端缓存机制,但如何将响应缓存到客户端以获得更好的性能呢?

背景

缓存是构建高性能和可伸缩 Web 应用程序最重要的方面之一。作为 ASP.NET AJAX 的关键功能之一,在构建使用 ASP.NET 的 AJAX 应用程序时,从客户端调用脚本方法将非常常用。以下是一个示例,展示如何启用服务器端缓存。

<asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Debug">
    <Services>
        <asp:ServiceReference Path="CacheService.asmx" />
    </Services>
</asp:ScriptManager>

<script language="javascript" type="text/javascript">
    var count = 0;
    
    function getServerTime()
    {
        window.count ++;
        CacheService.GetServerTime(onSucceeded);
    }
    
    function onSucceeded(result)
    {
        Sys.Debug.trace(result.format("HH:mm:ss"));
        
        if (count < 6)
        {
            window.setTimeout(getServerTime, 3000);
        }
        elses
        {
            window.count = 0;
        }
    }
</script>

<input type="button" value="GetCurrentTime" onclick="getServerTime()" />
<br /><br />

<textarea cols="20" rows="10" id="TraceConsole"></textarea>

这里有一个 ScriptManager 控件,其 ScirptMode 属性设置为 Debug,以便我们可以使用 Sys.Debug.trace 方法在 ID 为 "TraceConsole" 的 TextArea 元素中显示消息。当我们点击按钮时,我们将访问服务器上的方法六次以获取服务器时间,并且每次连续调用之间有 3 秒的间隔。脚本方法在服务器端定义如下:

[ScriptService]
public class CacheService  : System.Web.Services.WebService
{
    [WebMethod]
    public DateTime GetServerTime()
    {
        return DateTime.Now;
    }    
}

打开页面并点击按钮,我们将在页面上看到以下结果:

Screenshot - 1.png

服务器端缓存

ASP.NET AJAX 中的脚本方法访问具有内置的服务器端缓存机制,但似乎只有少数开发人员意识到这一点。他们通常会将数据缓存到 HttpContext.Cache 对象或其他位置,并在必要时从缓存中获取数据。这是开发 ASP.NET 应用程序时常见的缓存方式之一,但有时我们可以使用更方便、更高效的功能。请看下面的代码,了解如何启用此功能:

[WebMethod(CacheDuration=10)]
public DateTime GetServerTime()
{
    return DateTime.Now;
}

就像在 ASP.NET 中构建 Web 服务一样,我们使用相同的方式让 ASP.NET 缓存对具有相同内容的相同资源的请求,方法是设置 WebMethodAttributeCacheDuration 属性。如上面的代码片段所示,方法的返回值将在 10 秒内被缓存。System.Web.Extensions.dll 中的 System.Web.Script.Services.RestHandler 类的静态 InitializeCachePolicy 方法显示了如果我们这样做会发生什么:

private static void InitializeCachePolicy(WebServiceMethodData methodData, 
                                          HttpContext context)
{
    int cacheDuration = methodData.CacheDuration;
    if (cacheDuration > 0)
    {
        context.Response.Cache.SetCacheability(HttpCacheability.Server);
        context.Response.Cache.SetExpires(DateTime.Now.AddSeconds(
                                         (double) cacheDuration));
        context.Response.Cache.SetSlidingExpiration(false);
        context.Response.Cache.SetValidUntilExpires(true);
        if (methodData.ParameterDatas.Count > 0)
        {
            context.Response.Cache.VaryByParams["*"] = true;
        }
        else
        {
            context.Response.Cache.VaryByParams.IgnoreParams = true;
        }
    }
    else
    {
        context.Response.Cache.SetNoServerCaching();
        context.Response.Cache.SetMaxAge(TimeSpan.Zero);
    }
}

如果 ASP.NET AJAX 检测到要执行的方法设置了 CacheDuration,它将向当前 HttpCachePolicy 对象的 SetCacheability 方法传递 HttpCacheabibity.Server,以便请求的响应可以缓存以供将来使用。如果执行的方法包含参数,则当前 HttpCachePolicyVaryByParams 属性将通过 HttpCacheVaryByParams 对象将 "*" 项目设置为 true,以便 ASP.NET 为不同的参数组合缓存单独的响应。

让我们看看缓存的效果。

Screenshot - 2.png

与以编程方式缓存数据相比,设置 CacheDuration 属性以启用缓存的最重要优点是它非常易于使用。现在我们可以更多地专注于方法的实现,而无需处理缓存带来的麻烦(例如同步)。它还略微提高了性能,因为不需要将结果序列化为 JSON 字符串,而 ASP.NET 会负责将缓存的数据发送到客户端。但有时,自行缓存数据会更合适,因为它会节省许多资源。例如,这里有一个接受四个参数的方法,其中第二个参数指示其余参数将被忽略或不被忽略。

public string GetResult(int key, bool ignoreRest, string args1, string args2) { ... }

在上述场景中,几乎所有程序员都会仅为 key 参数的不同值缓存数据,而不会关心第二个参数设置为 true 时其余参数是什么。但是 ASP.NET 无法理解参数的含义,因此它会为任何组合缓存不同的数据副本,尽管结果相同。

客户端缓存

我使用了 HttpWatch 基础版 来捕获客户端和服务器之间的通信。这是快照。

Screenshot - 3.png

每次访问脚本方法时,相同的内​​容都会发送到服务器,并接收相同的响应。尽管响应会被缓存,但我们只节省了方法的执行时间,每次调用方法时仍然需要往返通信。这意味着,如果响应的大小很大或带宽较低,访问脚本方法对用户来说仍然是耗时的任务。因此,如果我们能够将响应缓存到客户端,以便用户在再次调用相同方法并使用相同参数时可以立即获得响应,那就更好了——即使网络连接丢失。

开始吧。

起初,如果我们想让浏览器为我们缓存响应,我们只能使用 HTTP GET 方法来访问脚本方法。

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public DateTime GetServerTime() { ... }

我们将使用传统方式将响应缓存到客户端。

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public DateTime GetServerTime()
{
    HttpCachePolicy cache = HttpContext.Current.Response.Cache;
    cache.SetCacheability(HttpCacheability.Private);
    cache.SetExpires(DateTime.Now.AddSeconds((double)10));
    cache.SetMaxAge(new TimeSpan(0, 0, 10));

    return DateTime.Now;
}

我们将 Cacheabilitiy 设置为 public(如果您想阻止响应被缓存并在不同客户端之间共享,private 也可以),并指定 10 秒的到期时间。由于 ASP.NET AJAX 已将此值设置为零(TimeSpan.Zero),因此我们也将其 MaxAge 设置为 10 秒。让我们检查一下效果……从未缓存?让我们看看其中一个响应中设置的标头。

Cache-Control    public, max-age=0
Date             Fri, 29 Jun 2007 00:44:14 GMT
Expires          Fri, 29 Jun 2007 00:44:24 GMT

问题是 Cache-Control 中设置的 max-age 值。我们已经将其设置为 10 秒,但由于 HttpCachePolicy 类的 SetMaxAge 方法的实现,它仍然是零。

public void SetMaxAge(TimeSpan delta)
{
    if (delta < TimeSpan.Zero)
    {
        throw new ArgumentOutOfRangeException("delta");
    }
    if (s_oneYear < delta)
    {
        delta = s_oneYear;
    }
    if (!this._isMaxAgeSet || (delta < this._maxAge))
    {
        this.Dirtied();
        this._maxAge = delta;
        this._isMaxAgeSet = true;
    }
}

调用 SetMaxAge 方法一次后,_isMaxAgeSet 标志被设置为 true,阻止 _maxAge 被设置为小于当前值的值。执行脚本方法时,_isMaxAgeSettrue_maxAgeTimeSpan.Zero,因此我们无法将其设置为任何其他值。是时候使用反射了。我们应该直接设置 _maxAge 值。

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
public DateTime GetServerTime()
{
    HttpCachePolicy cache = HttpContext.Current.Response.Cache;
    cache.SetCacheability(HttpCacheability.Private);
    cache.SetExpires(DateTime.Now.AddSeconds((double)10));
    
    FieldInfo maxAgeField = cache.GetType().GetField(
        "_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
    maxAgeField.SetValue(cache, new TimeSpan(0, 0, 10));
    
    return DateTime.Now;
}

让我们检查一下效果。

Screenshot - 4.png

似乎与之前的没有区别,但 HttpWatch 可以告诉我们这一点:

Screenshot - 5.png

没有发送和接收任何内容,因此缓存的响应确实已被“缓存”,并且客户端和服务器之间的往返通信已节省!请注意,缓存也是“特定于参数组合的”,这意味着如果您使用不同的参数,由于请求 URL(查询字符串)已更改,您将获得新响应。

客户端缓存的成功得益于浏览器收到的响应标头。请关注 Cache-Control 项中的 max-age 值。

Cache-Control    public, max-age=10
Date             Fri, 29 Jun 2007 00:54:32 GMT
Expires          Fri, 29 Jun 2007 00:54:42 GMT

在客户端缓存响应的最重要优点是它显着提高了性能。但它也有缺点。也许其中最严重的是反射的使用。反射操作在某些主机环境中有一定的限制。如果您想使用反射,您应该遵循以下三种方式之一。但实际上,在虚拟 Web 托管中几乎没有机会应用其中任何一种。

  • 在您的 Web 应用程序中使用完全信任。
  • 使用带有 ReflectionPermission 的自定义信任级别。
  • 将使用反射的代码放入单独的程序集中,并将其注册到 GAC。

客户端缓存响应的另一个缺点是,不同的客户端在缓存之前至少会执行一次服务器方法以获取响应。在这方面,服务器端缓存效果更好,因为服务器方法只会被执行一次(对于相同的参数组合),并且缓存的响应可以发送到所有客户端的请求。但我们可以最大程度地减少方法的执行时间,例如,像我们一直做的那样,在服务器端以编程方式缓存响应。

结论

在本文中,我们讨论了三种用于提高 ASP.NET AJAX 中脚本方法访问性能的缓存方法。现在是时候得出结论了。

  • 以编程方式缓存到服务器端: 这是三种方法中最灵活的一种。我们可以缓存任何我们想要的东西,并选择任何缓存策略。
  • 通过设置 CacheDuration 属性缓存到服务器端: 这是最简单的缓存方法。方法将只执行一次(对于相同的参数组合),并且缓存的响应可以发送到所有客户端的请求。有时,这效率不高,因为 ASP.NET 会缓存比我们实际需要更多的数​​据副本。
  • 客户端缓存: 缓存后,这可以提供最佳性能,因为节省了客户端和服务器之间的往返通信。但不同的客户端在缓存之前至少会执行一次服务器方法以获取响应。
© . All rights reserved.