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.
Thank you for this update Paul !!
I'm eager to test it especially being able to invalidate cached items.
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.
@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);
}
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 :)
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 )
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.
@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
@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.
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
@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.
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?
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
I was wondering the same as Khalid. It's a neat project but is it still active??