I'm using jQuery ajax to retrieve json from an ASP.Net MVC app and I'm not seeing expected results when making ajax calls where ifModified = true.

I'm using jQuery 1.9.1 and Chrome Version 26.0.1410.64 m

When using a new browser instance and making the initial request for content that is already cached, a call to the server to check the cache status is never made even though ifModified=true is set in the ajax config. On subsequent requests from the same browser instance, a call to the server is (expectedly) made to which the server responds with a 304.

On the initial request, the browser network trace shows the content was delivered from cache with a status 200 even though the modified date was never checked. Fiddler shows no request was made, and a breakpoint in the ASP.Net MVC Controller is not hit.

Is this a bug, or is there a way to get this to work properly? If it's a bug, is it a jQuery bug or a Chrome bug?

Here's a simplified ajax call:

var req = $.ajax({
    type: "GET",
    dataType: "json",
    url: "/JsonTest/GetJson",
    ifModified: true,
    success: function (data, status, jqXHR) {
        if (data === undefined && status == "notmodified") {
            $.ajax({
                type: this.type,
                dataType: this.dataType,
                url: this.url,
                ifModified: false,
                success: this.success,
                error: this.error,
                complete: this.complete
            });
        } else {
            alert($.toJSON(data));
        }
    },
    error: function (jqXHR, status, error) {
        alert(error);
    },
    complete: function (jqXHR, status) {
    }
});

Here's a simplified ASP.Net MVC controller method:

[AllowAnonymous]
public ActionResult GetJson()
{
    // dummy data version.
    var currentVersion = new DateTime(2013, 04, 30, 12, 0, 0);

    // get client cached version from request
    DateTime cachedVersion;
    if (DateTime.TryParseExact(HttpContext.Request.Headers["If-Modified-Since"], "r", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out cachedVersion))
    {
        cachedVersion = cachedVersion.ToLocalTime();
    }
    else
    {
        cachedVersion = DateTime.MinValue;
    }

    // return 304 if not changed
    if (currentVersion.Subtract(cachedVersion).Duration() < TimeSpan.FromSeconds(1))
    {
        HttpContext.Response.StatusCode = 304;
        HttpContext.Response.StatusDescription = "Not Modified";
        return new EmptyResult();
    }

    // still here? return current object and set current version response header
    var rv = new { name = "Hello", value = "World" };
    HttpContext.Response.Cache.SetCacheability(HttpCacheability.Private);
    HttpContext.Response.Cache.SetLastModified(currentVersion);
    return Json(rv, JsonRequestBehavior.AllowGet);
}

Steps to reproduce: 1) start new browser instance 2) initiate ajax request 3) close response alert window 4) initiate ajax request 5) close browser repeat

Very first time through, empty cache: step 2 issues request to server. Server responds with a 200. step 4 issues request to server, server responds 304.

Second time through - response in cache: step 2 DOES NOT issue request to server. Response is taken from cache with a 200 status. step 4 issues request to server, server responds 304.

I would expect step #2 on the second time through to issue a request to the server and receive a 304.

This failure to check the status of a cached source every time it's used is a big problem as cached data may be stale but the user won't know it until they issue a subsequent request in the same browser instance.

有帮助吗?

解决方案

Two things are happening here:

  1. Chrome is caching too aggressively.

  2. ifModified=true might not do quite what you think it does.

Chrome should always send a request to the server to verify the validity of a cached resource, but it appears that sometimes it simply doesn't. This behavior frequently frustrates Web developers who rapidly alter their resources during development.

JavaScript can only observe 304 responses when the script issuing the request explicitly sets cache headers. JavaScript cannot leverage cache-date information known by the browser; any cache-date information must be discovered or chosen by the script itself (probably by reading last-modified headers of previous responses). Actual 304 responses given to the browser will be reported to JavaScript as 200 responses, unless JavaScript set the If-Modified-Since header (or other cache headers).

The first fetch for a resource with jQuery will never return 304. This is because jQuery does not save values to use with the If-Modified-Since header between page loads. See my answer on How to check if jQuery.ajax() request header Status is “304 Not Modified”?:

jQuery does not persist cache information (e.g., in cookies) between page reloads, however. Therefore, the first fetch of a resource after a page reload will never be a 304, because jQuery has no cache information to send (i.e., we reset back to the "first fetch" case). There is no reason why jQuery couldn't persist cache information, but at present it doesn't.

The reason for this that 304 requests always come back empty. It's assumed that your code is storing the response the first time it comes back as a 200, but jQuery doesn't want to impose the requirement for you to save your cached result across page reloads.

其他提示

I solved the behavior issue by keeping track of initial requests and adding beforeSend to the ajax config:

window.resourceInitializers = {};
...
beforeSend: function (jqXHR, settings) {
    if (!window.resourceInitializers['GetJson']) {
        jqXHR.setRequestHeader("If-Modified-Since", "Sat, 01 Jan 2000 00:00:01 GMT")
        window.resourceInitializers['GetJson'] = true;
    }
},
...

The unfortunate side-effect is that the resource is always retrieved on a page reload regardless of its cache state.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top