In this article we will see how to precisely control the cache of the client
(the browser of your PC, for example) in order to avoid the transmission of data
from the server when the client already owns them. The ASP.NET server already
has systems for data caching, for example to avoid repeating the same database
query; would not it be nice not to avoid sending these data to avoid bandwidth
consumption?
The technique is called conditional GET and uses the
304 HTTP status code and in practice implements the following dialogue
between client and server:
Client: "Hello server, I need some data. The last time
you gave them to me, you told me they were update to the following date/time"
At this point, the server checks the provided date/time and, whether the data
are changed, it responds:
Server: "Hello client, the data have changed since then,
here's the new ones. If you need them again in future, note that data are update
to the following date/time."
or
Server: "Hello client, the data have not changed since
then, so I avoid send them again, you have them in your cache."
This type of optimization is particularly useful when data are dynamically
generated on the server side and change during the time.
Let us transform this dialogue in C# code, using the example of
an HttpHandler that generates a RSS file dynamically (that's
what happens when you subscribe to the RSS feeds of this blog). In this example
we do not address the topic of ther dynamic generation of RSS feeds, as this
would be beyond the scope of the article, but we'll just see how to implement
the conditional caching.
Let's assume that the method GetLastBlogUpdateDateTime()
returns
the UTC date/time of the last blog update.
public
class
BlogRssHandler : IHttpHandler, IRequiresSessionState
{
public
virtual
void
ProcessRequest(HttpContext Context)
{
// If the client already has the updated RSS, do not send it again
if
(BuildResponseHeader(Context) ==
false
)
return
;
else
{
// Generate the RSS feed
...
}
}
// Returns false if contents can be retrieved from the browser's cache
// (HTTP response 304 optimization)
private
Boolean BuildResponseHeader(HttpContext Context)
{
// Get the last modification date/time stored in the database
// NOTE: the date/time must be UTC
DateTime serverLastUpdateUTC = GetLastBlogUpdateDateTime();
// Get the last modification date provided by the browser (if any)
String ifModifiedSinceHeaderText = Context.Request.Headers.
Get(
"If-Modified-Since"
);
if
(!String.IsNullOrEmpty(ifModifiedSinceHeaderText))
{
DateTime clientLastUpdateUTC = DateTime.Parse(ifModifiedSinceHeaderText);
clientLastUpdateUTC = clientLastUpdateUTC.Value.ToUniversalTime();
// If the modification date/time of the client is equal to the server one
if
(serverLastUpdateUTC <= clientLastUpdateUTC)
{
// ...tell the browser that contents have not changed and return
Context.Response.ClearContent();
Context.Response.StatusCode = (Int32)
System.Net.HttpStatusCode.NotModified;
Context.Response.StatusDescription =
"Not Modified"
;
Context.Response.SuppressContent =
true
;
return
false
;
}
}
Context.Response.ContentType =
"text/xml"
;
// Tell the browser to cache the new contents
Context.Response.Cache.SetCacheability(HttpCacheability.Private);
Context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Context.Response.Cache.SetLastModified(DateTime.SpecifyKind(
serverLastUpdateUTC.Value, DateTimeKind.Utc));
// Force the browser to not use it's current cache
Context.Response.Cache.SetMaxAge(
new
TimeSpan(0, 0, 0));
Context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
return
true
;
}
...
}
When the server sends the date/time to the client it is better to round it
removing milliseconds.
It 'also important to specify that the date/time is UTC (DateTime.SpecifyKind
method), this thing is not mentioned in some examples around the web, but it is
very important.