question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow passing a Func instead of object in CachedPartial

See original GitHub issue

The CachedPartial extension is a great way to easily achieve partial caching. However, in our architecture, it has a great flaw. Calling the method requires passing in an already built model, and that is exactly where the possibly complex logic is that we want to cache!

Ideally, we pass a Func that builds the model, that is only executed if the partial is not cached already.

We have created our own extension for that, but unfortunately it is quite hacky, as there is no way to check if a certain partial is already cached. Instead, what we do is pass first null as model to CachedPartial. This will throw an exception if there is no cache yet built, but will return successfully if there is already a cache. We then catch the exception, building the model and calling CachedPartial again. The code is as follows (edited for brevity):

public static IHtmlString FuncCachedPartial(this HtmlHelper htmlHelper, string partialViewName, Expression<Func<object>> builder, int cachedSeconds = 3600)
{
    IHtmlString result = null;

    try
    {
        object model = null;
        result = htmlHelper.CachedPartial(partialViewName, model, cachedSeconds);
    }
    catch (Exception ex)
    {
        bool isCachedException = (ex.InnerException is InvalidOperationException && ex.InnerException.Message.Contains("but this dictionary requires a model item of type"));

        if (!isCachedException)
        {
            Diagnostics.Logging.Error(ex);
        }
    }

    if (result == null)
    {
        object model = builder.Compile()();
        result = htmlHelper.CachedPartial(partialViewName, model, cachedSeconds);
    }

    return result;
}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
ronaldbarendsecommented, May 18, 2021

Hi @rbottema! I’ve had a chat with Emma about this and although the PR for adding this in looks sensible, I’m a bit concerned about introducing this new logic:

  1. If the model is very expensive to create/build, why not only cache that value (and render using @Html.Partial())?
  2. This encourages adding more logic into the view (which is bad practice), especially when the function contains more boilerplate code for handling exceptions etc.
  3. You can make sure to build the model within the cached partial itself (based on the example in the PR, render using @Html.CachedPartial("~/Views/Partials/Home/Hero.cshtml", Model) and place the HeroBuilder.Build(Model) into the partial view) or even move this into a MVC action that’s called from the cached partial.
  4. The new @Html.CachedPartial() method signature is very similar to the existing one, which might cause ambiguous method calls, especially when omitting the optional parameters.

The last one is especially concerning, as the new overload might break existing calls to this extension method and thus existing sites that upgrade. So this would really need a good use case that’s currently not (easily) possible. Please do share this and/or give more insight into how/why, especially if I’ve missed something 😉

0reactions
rbottemacommented, Jun 2, 2021

Hi @ronaldbarendse,

I’ll answer your points one by one:

  1. Well that is kind of what’s happening here, but in this way we take advantage of Umbraco’s caching mechanism that already is in place (and can be different per page, cleared on publish, etc) instead of rolling our own.
  2. I don’t understand how this encourages adding more logic to the view, I would argue that it actually does the opposite: Because now the building of the model is also cached, you can more comfortably move logic to that builder. Or do you consider the () => HeroBuilder.Build(Model) call to be ‘logic in the view’ as opposed to the controller? In that case I still disagree: The bad practice stems from separation of concerns and reusability, and the logic being in a separate class fulfills those goals.
  3. If I understand your proposal correctly, I feel this would make the partial a lot less clean: The @model of the partial then becomes the Umbraco model instead of our ViewModel. If we want to for example now build the same partial from a different page type, we either would have to create a whole new partial that is only different in the first line, have some indirection (i.e.: the partial only contains the Build method and loading the other partial) or have some kind of type switching happening, all of which I strongly don’t prefer over this solution.
  4. I don’t think C# quickly confuses a Func<object> with an object, but if this is a large concern we can always rename the new overload.

I hope this clears up some stuff and makes you as enthusiastic about this as I am =)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cache partial view using Umbraco CachedPartial for ...
How to make cached partial by model parameter ? I tried to use @Html.CachedPartial("PartialView", MyModel, 3600, true, false ...
Read more >
Can function objects be passed as arguments for a closure ...
No, you cannot do that yet. struct S { func callAsFunction() {} } func method(_: () -> Void) {} let s = S()...
Read more >
Using MVC Partial Views in Umbraco
Partial views allow you to re-use components between your views (templates). ... private static Func<object, ViewDataDictionary?, string>?
Read more >
Passing functions into other functions as parameters, bad ...
It isn't a problem. It is a known technique. These are higher order functions (functions that take functions as parameters).
Read more >
WP-CLI – Page 12 – The command line interface for WordPress
Supports Requests installed to parent project in WP_CLIUtilshttp_request(); Passes thru --allow-root in cli update to let root update WP-CLI.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found