scalesJust finished writing the documentation for the most recent feature added to Spark. It provides output caching for sections of a template that could be costly in terms of rendering or data acquisition.

Part of this comes back to Phil’s blog post about Donut Hole Caching in ASP.NET MVC. Especially below where he writes, as he’s thinking out loud, “This would have to work in tandem with some means to specify that the bit of view data intended for the partial view is only recreated when the output cache is expired for that partial view, so we don’t incur the cost of creating it on every request.”

I have to admit I’m not necessarily a huge fan of the built-in ASP.NET page caching directives… Also didn’t particularly care for the forced alignment of partial-boundaries and cache-boundaries. I would hate to have a larger number of smaller files on disk simply because that’s the breakdown at which I wanted to cache the individual parts. In an ideal world I would like to be able to quickly and easily mark an expensive bit as cacheable, and provide the caching details that relate specifically to that bit, and be done with it.

So - enter Spark caching:

stuff
<cache key="employeeId" expires="300">
  this is stuff about the employee
</cache>
stuff

Kind of simple, really. The key works like the <%@OutputCache%> VaryByParam value, but it can be a comma delimited csharp expressions of anything which makes the cached information unique.

There’s a ICacheSignal interface which takes the place of custom CacheDependency classes in the ASP.NET caching framework.

public interface ICacheSignal
{
    event EventHandler Changed;
}

If you want fine-grained control to clear out-of-date cached data you would provide an instance of your implementation of this signal, or you can use the built-in ChangeSignal class. So that could be something as simple as:

stuff
<cache key="employeeId" signal="MyApp.Signals.EmployeeTables">
  this is stuff about the employee
</cache>
stuff
public static class Signals
{
  public static ChangeSignal EmployeeTables = new ChangeSignal();
}

You app would need to provide the glue to watch the tables and call Signals.EmployeeTables.FireChanged() of course. It does require a bit more effort, but you’re free to implement your signaling down to whatever level of granularity you would like. If you cache all of the comment html on a blog post, for example, you could keep a dictionary of change signals per-post-id around to drop exactly the correct amount of cached information when a comment is added or removed.

The implementation was a bit tricky in parts, but it should support the all of the cases where once attributes and named content are used inside or around cache elements.

Ah, yes! Back to the original need for avoiding the cost of gathering view data when the related bit of the view is cached. For that you’d use a trick with a ValueHolder class which enables you to keep your data acquisition code in the controller’s action, yet enables a cache hit to avoid the cost of that data acquisition.

public ActionResult Details(int id)
{
    var data = new NorthwindDataContext();

    ViewData["employeeId"] = id;

    ViewData["employee"] = ValueHolder.For(
        () => data.Employees.Single(x => x.EmployeeID == id));

    return View();
}

In that case for example you could have a cache key=”employeeId” and only use the employee.Value property inside that cache. That way if the html for that section (for that employeeId) has already been created and stored, then the second time through .Value property will never be used and the lambda will never be executed. For example:

<viewdata employeeId="int" employee="ValueHolder[[Employee]]" />
<cache key="employeeId">
  <h3>${employee.Value.FirstName} ${employee.Value.LastName}</h3>
  this is stuff about employee number ${employee.Value.EmployeeID}.
</cache>

So that’s a kind of nice way to cut out WCF and SQL costs without violating MVC best practices by having your views fetch data. After all it’s almost never the rendering costs that are the real problem in the first place.

There’s also a trick you can use with <viewdata> to get rid of the .Value. when you’re using the object inside a ValueHolder.

<viewdata employeeId="int" employee.Value="Employee employee" />
<cache key="employeeId">
  <h3>${employee.FirstName} ${employee.LastName}</h3>
  this is stuff about employee number ${employee.EmployeeID}.
</cache>

Updated, in answer to Adam’s question in the comments

A value holder can also by used inside of a strongly typed model, like this.

public class EmployeeDetailsModel
{
    // lazy-loaded data
    public ValueHolder<int, Employee> EmployeeHolder {get;set;}

    // aliasing .Key and .Value
    public int EmployeeID {get {return EmployeeHolder.Key;} }
    public Employee Employee {get {return EmployeeHolder.Value;} }
}

public ActionResult Details(int id)
{
    var data = new NorthwindDataContext();

    var model = new EmployeeDetailsModel
    {
        EmployeeHolder = ValueHolder.For(id,
            key => data.Employees.Single(x => x.EmployeeID == key))
    };

    return View(model);
}

And the usage in the view is about what you would expect.

<viewdata model="EmployeeDetailsModel" />
<cache key="Model.EmployeeID">
  <h3>${Model.Employee.FirstName} ${Model.Employee.LastName}</h3>
  this is stuff about employee number ${Model.Employee.EmployeeID}.
</cache>