Coming Soon: Caching in SPServices

For a long time, I’ve been reluctant to try to add caching to SPServices. The main reason is that I couldn’t think of a reliable way to do it that wasn’t going to cause potential issues and it also had to be backward compatible. Well, I think I’ve cracked that nut. In the current alpha of SPServices (v0.7.2ALPHA3), I’ve implemented a caching capability.

In my SPServices development environment, I have a ridiculous page which has far more calls to SPServices than you are likely to ever implement on one page yourself. It’s the page I use to test all of the potential combinations of calls that you might ever want to make to ensure that they work consistently. My testing is certainly not always foolproof, but this page has served me well to get most of the obvious kinks out for every version since the beginning of the library. (It runs in WSS 3.0, so I start there and move up the chain to test things with cross-version capabilities.)

Just to show how ridiculous it is, here are some counts:

Function Number of Calls
SPFilterDropdown 3
SPArrangeChoices 2
SPSetMultiSelectSizes 2
SPCascadeDropdowns 3
SPLookupAddNew 5
SPDisplayRelatedInfo 4
SPAutocomplete 1

At first glance, that may not seem so bad, but it takes 47 calls to various Web Service operations, and therefore 47 AJAX calls, just to load the page. It can take anywhere from 5-8 seconds on average, though sometimes it’s far worse.

After implementing the caching capability, I’ve cut that down from 47 to 27 calls, which is a 43% reduction, if I’m doing my math right. Even better, the load time is now consistently around 3.5 seconds. (I’m timing things in Firefox because Firebug makes it easy.)

Better yet is the performance improvement I see when I start changing values in the dropdowns, which causes more calls. The way I’ve implemented this, I’m caching the results for every single unique SOAP message SPServices sends to the server. This means that I’m not trying to cache all the items in any list, but only the XML which is returned for each request. Since each request has a unique “fingerprint” in the form of the SOAP message itself, I have a key to use to store the cached XML object and a way to retrieve it. The jQuery .data() function is what powers this simple little idea.

The way this works is somewhat clumsy: you only get a benefit if you make a call that is exactly the same as a prior call. However, that’s not as uncommon as you think, especially under the covers in the value added functions like SPCascadeDropdowns and most of the others above.  Many of the value added functions must call GetList for one reason or another, and just caching those calls cuts out something like 15% of the calls in my bloated page.

Now that I’ve posted this new alpha, I *really* need some help regression testing. Right now, the caching is all or nothing. There’s a default option called cacheXML, and it’s set to true in the alpha, so everything that can be cached will be cached. (When I release this functionality for real, the option will be set to false so that nothing works differently in existing applications.)

So here’s how you can help. Download the alpha and drop it into a test environment where you are already making SPServices calls. Try some tests with the alpha as it is, and then with the cacheXML option set to false. With Fiddler or Firebug, you should be able to see the SOAP traffic and get a gauge on what sort of improvement you might be able to gain. Let me know what you see and whether you have any problems. You can pass cacheXML: false into any call to SPServices if you need to turn off the caching for that call. Try some debugging, too, because I don’t want to make anything work differently than it has in the past.

The idea is simple, but it may well cause problems I haven’t thought about. Let me know how things go as a comment to this post, or better, in the SPServices Discussions, where it’s easier to post and format code. I think you’re going to like this!

Neat Trick to Ensure You’re Serving Up “Fresh” Script and CSS in SharePoint

This is a cool trick devised by one of my colleagues at my current client. (Confidentiality reigns supreme in client situations. Suffice it to say that this wasn’t my bright idea, but I give credit to the real thinker.)

We were having trouble with caching of .js and .css files which we were deploying to the server file system. When we deployed new versions, end users’ browsers weren’t always picking them up. (If you’re storing your .js and .css files in Document Libraries in SharePoint, move on. There’s nothing interesting for you here.)

Originally, someone had written a little piece of code which would go out and check the modified date on each file every time a page was loaded which used it. The modified date was then used as a Query String parameter on the file load, like this: ?rev=YYYYMMDD. This was potentially worrysomely inefficient, as we were checking the modified date every single time. However, without this check, we weren’t assured that the “fresh” stuff was getting where it needed to go.

The bright idea was to add a little code to the global.asa file on each Web Application. (Yes, this needed to go on every Web App, and that means every Web App on every Web From End server.) A bit of a PITA to deploy, but it’s a one-time thing.

Here’s the code:

<!--Assembly Name="Microsoft.SharePoint-->
<!--Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication"-->
<script>// <![CDATA[

    protected void Application_Start(object sender, EventArgs e)
        Application["DateLoaded"] = String.Format("{0:MdyyyyHHmmss}", System.DateTime.Now);

    protected void Application_End(object sender, EventArgs e)

// ]]></script>

This simply creates a global variable called DateLoaded every time the Web App is started. We then use the DateLoaded variable in each link or script reference, like this:

<link rel="stylesheet" type="text/css" href="/_layouts/MyProject/Styles/custom.css?rev=<%#Application["DateLoaded"]%>" />

You might be thinking that this isn’t perfect, and you’d be right. We’re asking the browsers to load fresh copies of the .js and .css files every time we restart the Web Apps. However, the overhead on the server is almost nil. We don’t look at the files themselves on each file reference, we just serve them up. Since we are storing the files in the file system and deploying with WSPs, everytime we do do, the Web Apps are restarted, so we get a new value for the DateLoaded variable. This causes the browsers to reload the files, but we can be absolutely certain that the users are getting the right stuff.

No more “it doesn’t look like you’re telling me” conversations. Slick, eh?