Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Don’t use Response.End() with OutputCache


:P
On this page:

This might be one of those “Duh” moment posts that seems real obvious now, but this particular issue – trying to get OutputCache to work in an HttpHandler and having it not work because of an errand Response.End() - has caused me grief on a few occasions. Some time ago I posted a JavaScript Resource handler that serves server side ASP.NET resources to client side JavaScript. Bertrand commented on that post that I should be using OutputCache instead of writing values to the ASP.NET Element Cache as I had been in the code posted in the blog entry. I agree – OutputCache is probably the most efficient way to cache full requests. However, in the past I’ve had a number of issues with OutputCache not working in custom handlers and so I’ve been wary of using it in handler code falling back on using the ASP.NET Element cache instead. The Element Cache works well enough, but there’s additional processing required on each hit that OutputCache handles transparently especially if you need to do special encoding like GZip, so OutputCaching is much preferred. You can see the original code I used that uses the element cache in the post. It certainly would be nice to use OutputCache.

Output Caching

To review, OutputCaching is part of the ASP.NET Pipeline and is the same mechanism that you can use in ASP.NET Pages using the @OutputCache directive. Using OutputCache with a handler rather than a Page is a little less discoverable than the page directive, but it provides the same functionality for any code running in the pipeline. OutputChacing is implemented as a Module in the pipeline and depending on the type of request that is being processed (headers, length, whether it’s a POST operation etc.) the caching can  get elevated to the IIS Kernel Cache (although information on when and how that actually occurs is very scarce). OutputCaching is probably the most efficient way to cache request because it is handled internally and doesn’t have to hit custom code if cached content is served. The content of a cached entry is fully held in the cache including headers, encoding and the content so there’s no requirement to re-encode or re-send headers etc. as you would when caching content on your own using the element cache as I did originally in the blog post code.

OutputCache is applied in its own step in the ASP.NET Event Pipeline via the ResolveRequestCache event which fires after request authorization (because you still want to authenticate/authorize cached requests) and before the request state is retrieved and a handler is loaded. For new entries the cache is then updated towards the end of the request in the UpdateRequestCache Event. You can see where in the pipeline these events fire on this old slide:

All this adds up to a very efficient mechanism of serving cached content because it fires very early in the pipeline processing and is handled internally by ASP.NET or (in IIS7) by IIS itself.

Writing to OutputCache is pretty straight forward and looks something like this:

HttpResponse Response = HttpContext.Current.Response; 
// client cache
if (!HttpContext.Current.IsDebuggingEnabled)
{                
    Response.ExpiresAbsolute = DateTime.UtcNow.AddDays(30);
    Response.Cache.SetLastModified(DateTime.UtcNow);
    Response.AppendHeader("Accept-Ranges", "bytes");
    Response.AppendHeader("Vary", "Accept-Encoding");
    //Response.Cache.SetETag("\"" + javaScript.GetHashCode().ToString("x") + "\"");
}

// OutputCache settings
HttpCachePolicy cache = Response.Cache;

cache.VaryByParams["LocaleId"] = true;
cache.VaryByParams["ResoureType"] = true;
cache.VaryByParams["IncludeControls"] = true;
cache.VaryByParams["VarName"] = true;
cache.VaryByParams["IsGlobal"] = true;
cache.SetOmitVaryStar(true);

DateTime now = DateTime.Now;
cache.SetCacheability(HttpCacheability.Public);

cache.SetExpires(now + TimeSpan.FromDays(365.0));
cache.SetValidUntilExpires(true);
cache.SetLastModified(now);
Response.ContentType = contentType;
Response.Charset = "utf-8";

Response.Write(text); 
Response.End(); // No caching if specified

The updated version of the JavaScriptResource handler that this code block is pulled from can be found in the West Wind Web Toolkit Repository here so you can compare with the original code from the original post.

Watch out for Response.End()

Now as it turns out the code as I have it written here DOES NOT work – as you can surmise from the highlighting the Response.End() actually makes this code run, but the output never makes it into the OutputCache. The reason for the Response.End() in the code here is that the code in question is a method in the handler rather than directly defined in ProcessRequest. Originally there was a bit of error checking code and cleanup that followed the output generation code in case there was a problem. There are also a number of error generation routines that create 404 requests on missing resources and errors on invalid syntax and so on – all of which also fire a Response.End(). So to be consistent I thought that using Response.End() everwhere would be the right thing to do.

Au contraire – using Response.End() in this fashion causes the processing of the pipeline to abort complete after output generation. In other words by specifying the Response.End() code at the end of the request causes UpdateRequestCache() to never fire and put the content into the cache. Response.End() (and HttpApplication.CompleteRequest()) causes the request to stop right at the point of the Response.End() call and jump to the EndRequest() handler, bypassing any events in between including UpdateRequestCache() which is needed to write the cache content into the OutputCache.

So, after a quick refactoring of the method and how post processing is handle I could remove the Response.End() call and voila the caching started working. The error method still do a Response.End() to exit early, but that’s acceptable because they should not cache anyway – they never actually hit the cache code (which is obviously important – you wouldn’t want to cache an error :-}).

This is pretty obvious once you know what’s happening, but I can tell you I looked at the code for quite some time agonizing over WHY the content was not getting into OutputCache even though it was staring me straight into the face. The Response.End() looked innocent enough.

Hopefully this post will help me – and maybe even one or two of you – jog my memory and avoid this problem in the future.

Posted in ASP.NET  

The Voices of Reason


 

DotNetKicks.com
May 22, 2009

# Don't use Response.End() with OutputCache

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Bahador
May 22, 2009

# re: Don’t use Response.End() with OutputCache

Thanks. It was very helpful to me right now. I wrote a handler last night, suffering from the same problem.

Milan Negovan
May 22, 2009

# re: Don’t use Response.End() with OutputCache

Indeed, Response.End() is wicked like that! I think it throws ThreadAbortException (which bit me a number of times).

ApplicationInstance.CompleteRequest() is a much gentler alternative, but---as you figured out---causes trouble with cache.

I did a somewhat similar write-up on my findings here: http://www.aspnetresources.com/articles/HttpFilters.aspx

Hope this helps!
Milan

Rick Strahl
May 22, 2009

# re: Don’t use Response.End() with OutputCache

@Milan - yeah Response.End() itself isn't all that evil - it's in components (or modules) where it can get really tricky. Application.CompleteRequest behavior is pretty much the same minus the ThreadAbort exception. Both will bypass the cache (as they should actually).

The moral of the story is to always spend a little time thinking about what side effects Response.End has especially in handlers and modules and if possible to not fire it so the rest of hte pipeline isn't affected.

BTW, I would expect the same behavior to be true inside of Page (or other Handler code) - a Response.End() call in Page is also going to keep the cache from being set.

Ryan
February 01, 2010

# re: Don’t use Response.End() with OutputCache

Great article, I just experienced this myself.

Although, this makes me ask, is there a way to end the handler/page execution but still continue the pipeline to allow the request to be processed and cached? What if you are in a page, and early in the event cycle, you send out separate content from the page and want to end the page's execution to prevent the rest of the events from occurring, but still want the response to be cached?

josh
September 26, 2013

# re: Don’t use Response.End() with OutputCache

Apparently the issue is the same with Response.Flush(). Removing that re-enabled caching support for me.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024