Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Tobias Klika 101 posts 570 karma points c-trib
    Jan 15, 2015 @ 18:09
    Tobias Klika
    0

    Using OutputCache with Archetype

    Hi!

    I don't have a question, I just want to present you my solution and want to discuss if this a good approach and what are its drawbacks.

    I am using Archetype to compose my pages.
    A page contains multiple modules (= ArchetypeFieldsetModel) which are stored as JSON in the database.

    Every module calls a SurfaceController which renders the partial view:

    @foreach (var module in Model.GetModules())
    {
      Html.RenderAction(module.Action, module.Surface, module);
    }
    

    I have created an "ArchetypeModule" for this purpose:

    public class ArchetypeModule
    {
      public PageViewModel Page { get; set; }
    
      public ArchetypeFieldsetModel Content { get; set; }
    
      public string Action { get; set; }
    
      public string Surface { get; set; }
    
      public string CacheId { get; set; }
    
      public List<ArchetypeFieldsetModel> GetList(string alias)
      {
        return Content.GetValue<ArchetypeModel>(alias).ToList();
      }
    
      public override string ToString()
      {
        return Surface + Action + CacheId;
      }
    }
    

    For the OutputCache I do need VaryByParam for sure. This is an example SurfaceController I am using:

    public class ExampleSurfaceController : SurfaceControllerBase
    {
      [ChildActionOnly]
      [OutputCache(Duration = 60, VaryByParam = "model")]
      public ActionResult Main(ArchetypeModule model)
      {
        // ...
      }
    }
    

    As the ArchetypeModule is a complex object, its ToString() method is called when the OutputCache uses it. That's why I've overridden the method to provide a CacheId, which is calculated like this:

    CacheId = JsonConvert.SerializeObject(fieldsetModel, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }).GetHashCode().ToString();
    

    So I am serializing the complete JSON representation of the data which was entered in the backoffice and get the hash code from it.

    What this does offer:

    1. A unique (or not so unique for hash code ;-) ) representation of the model I am using
    2. When the data is changed in the backoffice the CacheId will change too, therefore it will return the new (or previously cached) version of the partial.

    So, how do you think does this approach work?
    Do you see any drawbacks?

    The first test worked well. I have to admit that I am pretty pleased with this solution right now, but not sure if it's "perfect" ;-)

    Bye Tobi

    Update 1

    Looks like I have found the first drawback:
    When you have an Archetype, let's say "News Overview" which does render news items from other nodes, you can add/modify/delete these news but the archetype properties itself don't change. In this case the CacheId stays the same too.
    Possible solution: Clear OutputCache when changes occur in the BackOffice!?

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Jan 15, 2015 @ 18:47
    Nicholas Westby
    0

    I assume you are expecting the caching to improve performance, but I am confused about where you expect that performance increase to come from.

    It seems like you are first fetching the model using the Umbraco API (or so I assume), and then you are rendering the child action with that model. I would expect the rendering to happen very fast, and I would expect the fetching of the model to be slower. If the caching only happens after you have already fetched the model, I am not sure what performance gain you can expect.

    Furthermore, I think MVC doesn't cache the result of the rendering process. I could be wrong, but I think it caches the model you return from your action method (as an ActionResult), and then supplies that cached model to the CSHTML file each time it renders the page (if your CSHTML has DateTime.Now.ToString(), you'd see that update even with a cached result). That means that you are effectively not caching anything.

    I suppose the only thing that would be cached would be whatever happens in your "Main" action method. Supposing that function takes a lot of time (e.g., if you need to fetch a bunch of data based on your Archetype model), then I could see this caching being useful. Maybe that is the case as your update mentions a news overview... perhaps that would render info about child or picked news pages? I could see that taking some time to fetch, and so would be useful to cache.

    Disclaimer: I may be misinterpreting what you are doing.

  • Tobias Klika 101 posts 570 karma points c-trib
    Jan 15, 2015 @ 21:10
    Tobias Klika
    0

    Hi Nicholas.

    Thanks for your answer. The JSON model is stored the same as every other property in Umbraco, in the XML content cache.

    For your assumption that the partial view itself gets not cached, that's not true. On of the major benefits of OutputCaching (in my case Donut caching) is that the rendered view gets cached.

    enter image description here

    On the left side you can see the rendered page without OutputCaching (via Glimpse), on the right side with caching turned on. The rendering of the Page is faster because the partials are served from the cache. On complex pages you can save a few hundred ms here.

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Jan 15, 2015 @ 22:03
    Nicholas Westby
    0

    Looks like you are right about the partial being cached (just tested myself). I think I was confusing that with some form of caching in WebForms.

    And you are right that Archetype property values are stored in the XML cache just like every other property. I was not questioning that.

    I was just questioning what you were caching if you weren't caching the lookup of the Archetype model. I think I understand more clearly now that you have some logic in your action method that is taking some time. The approach you have outlined seems reasonable.

    As far as invalidating the cache, I do that by implementing GetVaryByCustomString in my derived UmbracoApplication with a call to HttpResponse.RemoveOutputCacheItem (that works with the built-in output caching, but a different technique would probably be needed if you use MvcDonutCaching).

    On a project I used MvcDonutCaching, I believe I invalidated the cache again in GetVaryByCustomString. Except, instead of invalidating the cache (I ran into some complication), I merely changed the cache key for any recently published page (I think I just stored a GUID that was associated with a page ID and that changed whenever a page was published).

    For your purposes, you'd want to want to do something slightly more sophisticated. You could invalidate the cache for any page picking a changed node (if you are using the built in output caching). Or, you could generate a composite key for each page that is composed of a GUID associated with that page and the picked pages, perhaps hashed if you'd like to keep the keys small (for MvcDonutCaching). These GUID's would be changed anytime a node changes (e.g., on publish/delete/move events).

    Could get complicated if you want to support an unlimited level of "picking", especially if there are circular references, but it's certainly possible.

    As of late, I don't rely on output caching. Instead, I just cache things at the application level and invalidate those caches using a few utilities I've built. For example, I have one cache that invalidates media information based on the media ID. And I have another that invalidates a cache based on matching document types. Works pretty well as far as I've seen. An example would be caching a menu that is shown in the header of every page. You can simply cache the menu in a static variable, and clone that variable making the necessary modifications to ensure the menu marks the active page.

Please Sign in or register to post replies

Write your reply to:

Draft