Click here to Skip to main content
15,884,425 members
Articles / Web Development / ASP.NET
Article

Client-side caching for script methods access in ASP.NET AJAX

Rate me:
Please Sign up or sign in to vote.
4.38/5 (4 votes)
28 Jun 2007CPOL7 min read 62.1K   36   3
As one of the key features in ASP.NET AJAX, calling a script method from a client would be used a lot when buliding AJAX applications using ASP.NET. There's a server side caching mechanism, but how can we cache the responses on the client side to get a better performance?

Background

Cache is one of the most important aspects of building high performance and scalable web applications. As one of the key features in ASP.NET AJAX, calling a script method from a client would be used a lot when building AJAX applications using ASP.NET. The following is a sample to show how to enable server-side caching.

ASP.NET
<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>

There's a ScriptManager control with the ScirptMode property set to Debug so that we could use the Sys.Debug.trace method to display messages in the TextArea element whose ID is "TraceConsole". We'll access the method on the server to get the server time for six times when we click the button, and there's a 3-seconds interval between each two successive ones. The script method is defined on the server-side like this:

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

Open the page and click the button, and we'll see the following result on the page:

Screenshot - 1.png

Cache on server-side

Script method access in ASP.NET AJAX has a built-in cache mechanism on the server-side but it seems that only a few developers realize that. They always cache the data in the HttpContext.Cache object or some other places and get the data from the cache, if necessary. That's one of the common ways of caching when developing ASP.NET applications, but we can use a more convenient and efficient feature some times. Please look at the following code to see how to enable this feature:

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

Like when building web services in ASP.NET, we use the same way to let ASP.NET cache the request for the same resource with the same content, by setting the CacheDuration property of WebMethodAttribute. As in the above code snippet, the result of the method would be cached for 10 seconds. The static InitializeCachePolicy method of the System.Web.Script.Services.RestHandler class in System.Web.Extensions.dll shows what happens if we do the following.

C#
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);
    }
}

If ASP.NET AJAX found that the CacheDuration is set for the method which is ready to execute, it would pass HttpCacheabibity.Server to the SetCacheability method of the current HttpCachePolicy object so that the result of the request would be cached for future use. If the executing method contains parameters, the "*" item would be set to true in the HttpCacheVaryByParams object by the VaryByParams property of the current HttpCachePolicy so that ASP.NET would cache separate results for different parameter combinations.

Let's see the effect of caching.

Screenshot - 2.png

Comparing with caching data programmatically, the most important advantage of setting the CacheDuration property to enable caching is that it's really easy to use. We can now focus more on the implementations of the methods and get rid of the troubles when working with cache (e.g., synchronization). It also improves the performance a little bit since it's not necessary to serialize the result into a JSON string but ASP.NET would take the responsibility to send the data cached to the client-side. But some times, caching data by ourselves is more appropriate since it would save many resources. For instance, here's a method which accepts four parameters and the second one indicates that the rest should be ignored or not.

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

In the scenario above, almost all programmers would cache the data only for the different values of the key parameter and will not care what the rest of the parameters are when the second one is set to true. But ASP.NET cannot realize the meaning of the parameters so it will cache different copies of data for any combination despite the same results.

Cache on client-side

I used the HttpWatch basic edition to capture the communication between the client and the server. Here's the snapshot.

Screenshot - 3.png

Each time we access the script method, the same content would be posted to the server, receiving the same result. Although the result would be cached, we only save the execution time of the method, and the round trips would be taken each time we invoke the method. It means that if the the size of the result is huge or the bandwidth is low, the accesses to script methods are still time-consuming jobs for the users. So it would be better if we can cache the result on the client-side so that the user can get the result immediately if we call the same method with the same parameters again - even if the network connection is lost.

Here we go.

At first, we can only use the HTTP GET method to access the script method if we want to let the browser cache the result for us.

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

We'll use the traditional way to cache the result on the client-side.

C#
[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;
}

We set the Cacheabilitiy to public (private is also OK if you want to prevent the response being cached and shared among different clients) and specify a 10-seconds expiration time. We also set the MaxAge to 10 seconds since ASP.NET AJAX has already set this value to zero (TimeSpan.Zero). Let's check the effect...never cached? Let's see the header set in one of the responses.

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

The problem is the value of max-age set in Cache-Control. We have already set it to 10 seconds but it's still zero due to the implementation of the SetMaxAge method of the HttpCachePolicy class.

C#
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;
    }
}

After calling the SetMaxAge method once, the _isMaxAgeSet flag is set to true, preventing _maxAge from being set to a value smaller than the current one. When executing the script method, _isMaxAgeSet is true and _maxAge is TimeSpan.Zero so we cannot set it to any other value. It's time to use reflection. What we should do is set the _maxAge value directly.

C#
[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;
}

Let's check the effect.

Screenshot - 4.png

Seems no different from the previous one, but HttpWatch could tell us this:

Screenshot - 5.png

Nothing is sent and received, so the cached responses have really been "cached" and the round trips between the client and the server have been saved! Please note that the cache is also "parameter combination specific", which means if you use different parameters, you'll get the new result since the request URL (query string) has been changed.

The cache on the client-side is successful due to the response header the browser received. Please focus on the max-age value in the Cache-Control item.

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

The most important advantage of caching responses on client-side is that it improves the performance significantly. But it has disadvantages. Perhaps, the most serious one of them is the usage of reflection. Reflection operations are always restricted in some kind of host circumstance. If you want to use reflection, you should follow one of three ways. But actually, there's few chance to apply any one of them in a virtual web hosting.

  • Use full trust in your web application.
  • Use customized trust level with ReflectionPermission in it.
  • Put the code using reflection in a separate assembly and register it into GAC.

Another disadvantage of caching responses on the client-side is that different clients would execute the server method at least once to get the result before caching. In this aspect, server side caching works better since the server method would be executed only once (for the same parameter combinations) and the cached result could be sent to the requests from all clients. But we could minimize the execution time of the method, e.g., cache the result programmatically on the server-side as we always do.

Conclusion

We have talked about three ways of caching to improvement the performance of script methods access in ASP.NET AJAX, in this article. It's time to draw a conclusion.

  • Cache on server side programmatically: It's the most flexible one among the three ways. We could cache anything we want and select any kind of cache policy.
  • Cache on server side by setting the CacheDuration property: It's the easiest way to cache. The method would be executed only once (for the same parameter combinations) and the cached result could be sent to requests from all clients. Some times, this is not so efficient since ASP.NET would cache more copies of data than we actually need.
  • Cache on client side: This has the best performance once cached since the round trips between the client and the server are saved. But different clients would execute the server method at least once to get the result before caching.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
China China
Jeffrey Zhao(http://blog.jeffzon.net) has been a developer since 1997 and is working as a developer in .NET platform since 2002. He's now a develop manager in charge of building a media platform in a Chinese company. He's also a part-time technical consultant & trainer focus on .NET, AJAX and SilverLight technologies.

Comments and Discussions

 
GeneralSupporting Jeffrey Zhao Pin
Dejun Hou15-Oct-07 1:35
Dejun Hou15-Oct-07 1:35 
GeneralAlready Covered! Pin
geekswith.blog29-Jun-07 4:16
geekswith.blog29-Jun-07 4:16 
GeneralRe: Already Covered! Pin
Jeffrey Zhao29-Jun-07 6:35
Jeffrey Zhao29-Jun-07 6:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.