Output caching dynamic pages

M

Martin

Hi.

I had a very frustrating afternoon and evening but I have got it all under
control now so all of a sudden I am in a good mood. I want to share some
insights on output caching with you lot.

After looking at the use of the OutputCache directive and tinkering with it
a bit I found its usability.very limited. Think of it: it is okay for static
content. Well that's nice but no big deal. Static content is cheap anyway,
the file system cache will take care of caching static content for me
automatically, ASP.NET doing it once again seems more like a waste to me
than an efficiency boost.

So much for caching static content. For dynamic content it is absolutely
useless because there is no way to invalidate the cache based on a parameter
value which is the one thing you want when applying output caching on a
dynamic page. Take a message board page. When someone posts a message, you
would do something like

<form id="MsgForm" action="board.aspx?action=post" method="Post">

so you can detect the post-back based on the value of the action parameter.
You would switch to code that saves the message and then redirect to display
the updated message list. Of course, at that time you would want the cache
to be invalidated in order not to have the old message list being returned
instead of your save logic being invoked. Currently there is no way to deal
with this using the OutputCache directive. All you can do with VaryByParam
is add more cached pages, you cannot exclude a page based on a parameter
value which is what you need here.

It doesn't seem hard for the ASP.NET developers to expand the OutputCache
directive to support this kind of behavior and I don't see why they did not
do so in the first place. Code that invalidates a page isn't of much use
while ASP.NET keeps serving cached responses, disregarding any server side
logic. It's catch 22, you cannot pull yourself from the swamp pulling your
bootstraps.


So, this morning I hit something interesting:
System.Web.HttpCachePolicy.AddValidationCallback. At last, this seemed like
what I had been looking for all along: a call-back. I would be asked whether
it was okay to use the cached page or not. How convenient, I, the
programmer, actually got a say in the way requests were handled again. Nice!

I had three kinds of request to deal with for the same page. First there was
the parameterless request, listing all previously posted data. Then there
was the edit request for which the response is a form to be filled in and
posted back by the user. And finally there was the post request for which
the posted form is received , the data collected and stored and a redirect
is performed to simulate the first type of request.

What took me about 4 hours to figure out is that for the post request my
call-back never gets called. This makes sense, the logic behind my post
request does not produce a response, it just stores data and that's it. If
there's no response, there will be no cached data for the request hence
there is nothing to query for ASP.NET.

At this point it may seem simple. Why bother with call-back routines if we
do get the post request at all times, just when we need to take control? The
problem is that you don't have the context for normel (parameterless)
request at that time so you cannot invalidate it when you want to. Perhaps
there is a way I am not aware of to get to a page's cache from within
another context after al. If so I would like to hear about it. I found
another solution.

The way to make it work just perfectly is to declare a static flag in your
page class, have that set by your store logic (that gets invoked by the post
request) and then check that flag in the call-back implementation for a
normal request. If the flag is set it means we had a new post and the cached
list is not current anymore so we need to invalidate. You should also reset
the flag.


The aspx file could have an ordinairy OutputCache directive:

<%@ OutputCache Duration="16934400" VaryByParam="*" Location="Server" %>
<!-- keeping page hot for 4 weeks -->

and on any normal request expiration could be refreshed to the same same
amount ().

Below are the interesting parts of the code-behind file.


namespace mwte
{
using System;
using System.Web;

public class MyPage : System.Web.UI.Page
{
public string URL_of_this_page = null;

static Boolean IsDirty = false;

static protected void Validate(HttpContext context, Object data, ref
HttpValidationStatus status)
{
string action = (context.Request["action"] == null) ? "" :
context.Request["action"];

// The default page should only expire when IsDirty
// was set because of an earlier post request.
if (action == "")
{
if (IsDirty)
{
status = HttpValidationStatus.Invalid;
// reset flag
IsDirty = false;
}
else
{
status = HttpValidationStatus.Valid;
// reset expiration time to a day from this moment on
context.Response.Cache.SetExpires(DateTime.Now.AddDays(1));
}
return;
}

// the edit form never expires
if (action == "edit")
{
status = HttpValidationStatus.Valid;
return;
}

// Note that we do not test for action == "post" here. While it would
// be nice if we could, we can't because Validate is not called for
// post requests. There is no response for post requests, hence no
// cached page, hence Validate is not called. We need to set IsDirty
// in Page_Load instead.
}

protected void Page_Init(object sender, EventArgs e)
{
URL_of_this_page = Request.ServerVariables["SCRIPT_NAME"];
Response.Cache.AddValidationCallback(new
HttpCacheValidateHandler(Validate), null);
}

protected void Page_Load(object sender, EventArgs e)
{
// no local caching
Response.Expires = 0;

string action = (Request["action"] == null) ? "" : Request["action"];

if (action == "")
{
// keep page hot another 4 weeks
Response.Cache.SetExpires(DateTime.Now.AddDays(28));

// aspx with asp:Xml control does it all
return;
}

if (action == "edit")
{
// let aspx respond with input form
return;
}

if (action == "post")
{
// store logic
// ...

// signal next request handler to invalidate the cached page
IsDirty = true;

// display (and cache) the new list
Response.Redirect(URL_of_this_page);
return;
}
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top