What's New In MvcDonutCaching 1.1?

A new version of MvcDonutCaching has just been released with even more features plus several bug fixes. Following user feedback, this release centres on allowing MvcDonutCaching to be used in place of the built-in OutputCache attribute in many common scenarios. This should mean that you can take advantage of the many MvcDonutCaching benefits without losing core OutputCache functionality. V1.1 adds support for non-view action results and cache location specification and also fixes several known bugs.

The MvcDonutCaching NuGet package was initially released to provide donut caching capabilities to ASP.NET MVC 3 - something that is not possible with the built-in OutputCache. Whilst this was the primary feature of the product, we also added a number of other improvements, particularly around invalidating existing cache items - something that was previously rather awkward. Surprisingly (for us), many people started using the package exclusively for these secondary features and not just when donut caching was required.

The key features that have been added in this release are:

Support for non-view ActionResults

The previous version of MvcDonutCaching allowed you to cache Views and PartialViews which is fine if we use the package exclusively for donut caching scenarios but not so useful if we want a general purpose OutputCache replacement.

This release has been refactored to allow more support for different action results. All text based file types (e.g. HTML, XML, JS, JSON, RSS...) should now work. I will look into supporting binary files for the next release if there is enough demand.

[DonutOutputCache(Duration = 10)]
public ActionResult JavaScriptTest()
{
    var message = string.Concat("hello from cached @", DateTime.Now.ToLongTimeString());

    return JavaScript("alert('" + message + "');");
}

[DonutOutputCache(Duration = 10)]
public ActionResult JsonTest()
{
    var obj = new
    {
        This = "hello from cached @" + DateTime.Now.ToLongTimeString(),
        That = 42
    };

    return Json(obj, JsonRequestBehavior.AllowGet);
}

[DonutOutputCache(Duration = 10)]
public ActionResult XmlTest()
{
    var xml = new XDocument(
      new XElement("test",
      new XElement("foo",
      new XAttribute("time", DateTime.Now.ToLongTimeString()),
      new XElement("bar", "1"),
      new XElement("bar", "2"),
      new XElement("bar", "3"))));

    return Content(xml.ToString(), "text/xml");
}

The ability to use the cache location enumeration

The built-in OutputCache allows you to control caching beyond the server. The OutputCacheLocation enumeration allows you to specify how the client and intermediate proxy servers handle the content. You can now specify a location value for your DonutOutputCache attributes, though you should be aware of how this may affect other MvcDonutCaching features.

Caching on the server (and nowhere else) is the default option. This gives you the greatest control, allowing you to take advantage of donut caching and enabling you to remove an item from the cache at any point. Once you allow a client or proxy server to cache content, you lose some of this control but depending on your situation, this may be acceptable. If you use donut caching with client caching, the donut hole will only be refreshed for new users visiting the page or if you force a page refresh with CTRL F5. Similarly, if you remove a page from the cache using the OutputCacheManager, it will only be removed from the server cache. Existing clients will continue to use any cached pages until they expires (or until the user forces a refresh). This is all fairly obvious - you cannot remove browser cache items from the server, but it is worth mentioning nevertheless.

[DonutOutputCache(Duration = 600, Location = OutputCacheLocation.ServerAndClient)]

Using the Html.Action overload in Layout Pages

You can now use the excludeFromParentCache=true Html.Action overloads without using the DonutOutputCache attribute on the controller action. Why is this useful? Several people pointed out that you might want to render an action from a layout page. In these cases, you may have some pages that use the layout and need to be cached and others that don't need to be cached. If you want the layout action to be a donut hole and get updated on every request, you will need to use the excludeFromParentCache=true Html.Action overload but in previous version of MvcDonutCaching, this caused a problem with non-cached pages - the action was not rendered. This has now been addressed and the action will be rendered in all cases.

As I explained in the first post on MvcDonutCaching, the Html.Action overload renders an HTML comment containing serialised details about the action. This is necessary to allow the action to be updated whenever the view is retrieved from the cache - i.e. to enable the donut caching. Normally the DonutOutputCache attribute removes the comment before the view output is returned to the client, but in this new scenario, there isn't necessarily a DonutOutputCache on the action, so the comment will not always be removed. Exposing internal information about an action (controller name, action name and route values) within the outputted HTML is not acceptable, so we now encrypt this information by default.

Conclusion

MvcDonutCaching 1.1 introduces a number of new features and bug fixes and it is strongly recommended that you update as soon as possible. Whilst version 1.0 primarily dealt with the ability to perform donut caching, the additional benefits that we added made it a popular choice as a general OutputCache replacement. Version 1.1 addresses some of the shortcomings of the previous version in this regard. As always, please report any bugs on CodePlex.

Useful or Interesting?

If you liked the article, I would really appreciate it if you could share it with your Twitter followers.

Share on Twitter

Comments

Avatar for Nicolas Nicolas wrote on 04 Jan 2012

Thank you for this update Paul !!
I'm eager to test it especially being able to invalidate cached items.

Avatar for Chris Marisic Chris Marisic wrote on 04 Jan 2012

Paul,

I updated to the 1.1 release today and do not see any caching happening for my actions, just the same as I reported back on the previous post from December.

My Action completes as

var result = JsonConvert.SerializeObject(response);
HttpContext.Response.Write(result);
return new EmptyResult();

For argument-sake I changed my code to use the built in Json and

return Json(response);

However neither of these resulted in my content actually being cached. I can set a break point inside my controller action method and refresh the page and watch the break point get called every time.

Avatar for Paul Hiles Paul Hiles wrote on 05 Jan 2012

@Chris - All the controller actions listed below are correctly cached for me. Do these work for you and if so, how do your requirements differ? If you still think there is a bug, submit it on CodePlex with a way for me to replicate it and I will sort it out asap.

[DonutOutputCache(Duration = 10)]
public ActionResult Json()
{
var obj = new
{
This = "hello from cached @" + DateTime.Now.ToLongTimeString(),
That = 42
};

return Json(obj, JsonRequestBehavior.AllowGet);
}

[DonutOutputCache(Duration = 10)]
public ActionResult JsonResponseWrite()
{
var obj = new
{
This = "hello from cached @" + DateTime.Now.ToLongTimeString(),
That = 42
};

var result = new JavaScriptSerializer().Serialize(obj);

HttpContext.Response.Write(result);
HttpContext.Response.ContentType = "application/json";

return new EmptyResult();
}

[DonutOutputCache(CacheProfile="Blah")]
public ActionResult JsonCacheProfile()
{
var obj = new
{
This = "hello from cached @" + DateTime.Now.ToLongTimeString(),
That = 42
};

return Json(obj, JsonRequestBehavior.AllowGet);
}

[DonutOutputCache(Duration = 10, VaryByParam="blah")]
public ActionResult JsonVaryByParam(string blah)
{
var obj = new
{
This = "hello from cached @" + DateTime.Now.ToLongTimeString(),
That = 42,
Other = blah
};

return Json(obj, JsonRequestBehavior.AllowGet);
}

Avatar for Korayem Korayem wrote on 11 Jan 2012

Thumbs up for "Ability to use the cache location enumeration". Glad that my discussion back in the older thread made sense. Thanks.

As for the reported issue that I posted on codeplex, I will send you a more detailed analysis to find why did this occur.

Awesome work and keep it up :)

Avatar for ILICH ILICH wrote on 12 Jan 2012

Thank you guys, it's very useful library. Is there any way to skip controller instance creation for cached requests ? This extra creation seems redundant and in my case creates controller instance about 6 times per some request ... which is a bit overkill )

Avatar for Phil Phil wrote on 16 Jan 2012

I've been using your excellent MvcDonutCaching library and have run into a serialization problem with an MVC action that passes through a viewModel.

htmlHelper.Action(ControllerAction.OpinionExtras, ControllerName.Opinions, new {dataViewModel = postOpinionDataViewModel}, true);

Type 'Presentation.Web.ViewModels.PostOpinionDataViewModel' with data contract name 'PostOpinionDataViewModel:http://schemas.datacontract.org/2004/07/Presentation.Web.ViewModels'; is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

I guess this is because the DataContractSerializer doesn't know how to serialize this type. Any thoughts on what modifications could be made in ActionSettingsSerialiser.cs to remedy this problem? I don't really want to modify the DataContractSerializer constuctor to include ever viewModel type in my project, but I can't think of any other way.

Avatar for Paul Hiles Paul Hiles wrote on 24 Jan 2012

@Phil - I think you are trying to use the Html.Action helper like an Html.Partial, rather than as a way to invoke a sub-request which is the intended use. The third argument of the Html.Action overload in your example is for route values rather than a view model - you would typically only pass the route data necessary for the child controller action to be able to execute and generate its own view model, rather than the view model itself.

If I am mistaken and this answer is too simplistic, I apologise in advance and ask that you report the issue on codeplex, explaining more about the structure of the PostOpinionDataViewModel and I will take a look.

http://mvcdonutcaching.codeplex.com

Avatar for Paul Hiles Paul Hiles wrote on 24 Jan 2012

@ILICH - Not sure we can do much about this. The instantiation should be very light though. If you are injected some expensive dependencies, take a look at the article on lazy loading with Unity:

http://www.devtrends.co.uk/blog/using-unity's-automatic-factories-to-lazy-load-expensive-dependencies

For a page that is not currently in the cache, the controller will be instantiated for your main page action and then for each child action. This is just the MVC framework and we cannot reduce this number. For cached pages, the main page action and all child actions that are configured as donut holes will result in a controller instantiation. I do not think we can get rid of the main page instantiation because this is where the framework will pickup the DonutOutputCache attribute on the controller. If there are multiple child actions from the same controller, it may be possible to keep the controller in memory between the child actions but this is not going to offer a significant improvement. Maybe it is something we can look at in a future version though.

Avatar for Dylan Morley Dylan Morley wrote on 28 Feb 2012

Hi,

Ran into problems trying to use the DonutCache attribute along with Memcached for distributed output caching. It's a really simple fix though, I've created a work item on Codeplex

http://mvcdonutcaching.codeplex.com/workitem/2482

Thanks

Avatar for Paul Hiles Paul Hiles wrote on 28 Feb 2012

@Dylan - Yes, I saw your item on CodePlex this morning. Thanks for taking the time to come up with a fix. I'll add the Serializable attribute as per your suggestion in the next release.

Avatar for John John wrote on 15 Mar 2012

First let me say how impressive and elegant your solution is. We have been looking for something like this for a while and it works perfectly. One issue I am having is in trying to run this with a distributed output cache (memcached) I am getting decryption errors. According to Microsoft, the RijndaelManaged class cannot be used to read encrypted data from other instances/machines as the initialization vector will be different even if the same secret key is used (http://support.microsoft.com/kb/842791/en-us). The same class has to be used in decryption of the data as was used to encrypt the data. Is this right?

Avatar for Khalid Abuhakmeh Khalid Abuhakmeh wrote on 04 Feb 2013

Hello Paul, I was wondering if you were still maintaining this project. I see there are a few issues still pending on CodeProject and wanted to know if they were going to be merged in and released as a new version on NuGet.

Thanks,

Khalid

Avatar for Jason Jason wrote on 16 Mar 2013

I was wondering the same as Khalid. It's a neat project but is it still active??