5
votes

In an ASP.NET site, I would like to add an "Expires" header to certain static files, so I added a clientCache configuration like this for the folder where these file are:

<system.webServer>
  <staticContent>
    <clientCache cacheControlMode="UseExpires" httpExpires="Wed, 13 Feb 2013 08:00:00 GMT" />
  </staticContent>

If possible, I would like to compute the value of httpExpires programmatically, to set it for example to the time the file was last updated + 24 hours.

Is there a way to configure the cache control to get the value of httpExpires by calling a method?

If not, what are the alternatives? I thought about writing a custom http handler, but maybe there is a simpler solution...

EDIT: please note that these are static files, so they are not served by the regular asp.net page handler.

2

2 Answers

7
votes

You can use Response.Cache to set caching programmatically.

Here's a nicely looking tutorial.

Basically you want to set the caching policy to Public (clientside + proxies) and set the expiration header. Some of the methods are named rather awkwardly, but this one is easy enough.

HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
HttpContext.Current.Response.Cache.SetExpires(yourCalculatedDateTime);

(ASP.NET designers didn't like the Law of Demeter much, did they?)

Alternatively you can access the heareds via Response.Headers collection, where you can change them explicitly.

Both these ways are accessible in all ASP.NET handlers (aspx, asmx) and could be changed probably everywhere where you can access the current HttpContext.

6
votes

Thanks to @HonzaBrestan, who put me on the right track with the HTTP Module idea, I came up with a solution like this one, which I want to share in case it will be useful for someone else.

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;

public class ExpirationModule : IHttpModule {

    HttpApplication _context;

    #region static initialization for this example - this should be a config section

    static Dictionary<string, TimeSpan> ExpirationTimes;
    static TimeSpan DefaultExpiration = TimeSpan.FromMinutes(15);
    static CrlExpirationModule() {       
        ExpirationTimes = new Dictionary<string, TimeSpan>();
        ExpirationTimes["~/SOMEFOLDER/SOMEFILE.XYZ"] = TimeSpan.Parse("0.0:30");
        ExpirationTimes["~/ANOTHERFOLDER/ANOTHERFILE.XYZ"] = TimeSpan.Parse("1.1:00");
    }

    #endregion

    public void Init(HttpApplication context) {
        _context = context;
        _context.EndRequest += ContextEndRequest;
    }

    void ContextEndRequest(object sender, EventArgs e) {
        // don't use Path as it contains the application name
        string requestPath = _context.Request.AppRelativeCurrentExecutionFilePath;
        string expirationTimesKey = requestPath.ToUpperInvariant();
        if (!ExpirationTimes.ContainsKey(expirationTimesKey)) {
            // not a file we manage
            return;
        }
        string physicalPath = _context.Request.PhysicalPath;
        if (!File.Exists(physicalPath)) {
            // we do nothing and let IIS return a regular 404 response
            return;
        }
        FileInfo fileInfo = new FileInfo(physicalPath);
        DateTime expirationTime = fileInfo.LastWriteTimeUtc.Add(ExpirationTimes[expirationTimesKey]);
        if (expirationTime <= DateTime.UtcNow) {
            expirationTime = DateTime.UtcNow.Add(DefaultExpiration);
        }
        _context.Response.Cache.SetExpires(expirationTime);
    }

    public void Dispose() {
    }

}

Then you need to add the module in the web config (IIS 7):

<system.webServer>
  <modules>
    <add name="ExpirationModule" type="ExpirationModule"/>
  </modules>
</system.webServer>