Lecoati / LeBlender

LeBlender is an open source Umbraco backoffice extension which made the Grid Canvas Editors management easier and flexible.
30 stars 63 forks source link

Cache by member #96

Open bjarnef opened 6 years ago

bjarnef commented 6 years ago

At the moment the caching of the LeBlender grid editors is by page, but it would be useful to cache by member as when using the CachedPartial e.g. when you present specific content inside the editor depending on the member type who is logged in.

IHtmlString CachedPartial(
    string partialViewName,
    object model,
    int cachedSeconds,
    bool cacheByPage = false,
    bool cacheByMember = false,
    ViewDataDictionary viewData = null,
    Func<object, ViewDataDictionary, string> contextualKeyBuilder = null)

https://24days.in/umbraco-cms/2017/the-one-with-performance/performance-boosts-for-umbraco/

https://our.umbraco.com/projects/backoffice-extensions/leblender/general-discussion/92538-caching-of-user-specific-content-in-leblender

bjarnef commented 6 years ago

@soreng do you have a suggestion how to solve cache by member for LeBlender grid editors?

soreng commented 6 years ago

@bjarnef I would suggest not caching the content for this.

Maybe you can use output caching in the page-controller, to get a similar result

bjarnef commented 6 years ago

@soreng I tried with [OutputCache] on the LeBlender controller Index method, but didn't seem to work when I rendered DateTime.Now.

public class CategoryProductListController : LeBlenderController
{
    [OutputCache(Duration = 3600, VaryByParam = "None", VaryByCustom = "User", Location = OutputCacheLocation.Server)]
    public ActionResult Index(LeBlenderModel model)
    {
        return View(model);
    }
}

However inside the grid editor I have some condition to render different actions from a SurfaceController, which returns another view e.g.

Html.RenderAction("GetProducts", "ProductList", new { ids = productPickerModel.SelectedProducts, pageSize = 12, mode = "carousel" });

When I use [OutputCache] on this action method DateTime.Now in this view is cached.

[OutputCache(Duration = 3600, VaryByParam = "None", VaryByCustom = "UserType")]
 public ActionResult GetProducts([ModelBinder(typeof(IntArrayModelBinder))] int[] ids, int page = 1, int pageSize = 10, SortBy sortBy = SortBy.None, SortDirection sortDirection = SortDirection.Ascending, string mode = "")
{
    int total = 0;
    var products = MyRepo.GetProducts(ids, page, pageSize, out total, false, sortBy, sortDirection);

    var pager = new PagerModel(pageSize, page - 1, total);
    var listModel = new ListModel(products, pager);

    ViewData["mode"] = mode;

    return View("/Views/Partials/ProductList.cshtml", listModel);
}
bjarnef commented 6 years ago

@soreng would it be possible to make this LeBlenderPartialCacher https://github.com/Lecoati/LeBlender/blob/e11905f192a1cb733ec012404998ff74f0b56ca5/Src/Lecoati.LeBlender.Extension/LeBlenderPartialCacher.cs

similar to CachedPartial in core? https://github.com/umbraco/Umbraco-CMS/blob/7ee510ed386495120666a78c61497f58ff05de8f/src/Umbraco.Web/HtmlHelperRenderExtensions.cs#L80

soreng commented 6 years ago

I dont see why it could’nt just use the method from core directly - but keep in mind LeBlender is build Against umbraco 7.4.

But back to the question. It requires the value of cacheByMember to be true somehow. My concern on this was that it could add alot stuff to memory if we are not carefull. It could be done via an attribute on the controller maybe?

bjarnef commented 6 years ago

@soreng it seems this extension class haven't changed much the last couple of years https://github.com/umbraco/Umbraco-CMS/commits/7ee510ed386495120666a78c61497f58ff05de8f/src/Umbraco.Web/HtmlHelperRenderExtensions.cs

When the core CachedPartial allowing caching by member and macros has this feature, would it make sense to allow this for LeBlender grid editors as well?

image

Furthermore the CachedPartial has a contextualKeyBuilder parameter, which can be used in e.g. a multi-site solution where same partials are used in each site, in this case it will be cached by domain.

@Html.CachedPartial("~/Views/Partials/MainMenu.cshtml", Model.Content, 1209600, contextualKeyBuilder: (model, viewData) => Request.Url.Host)

I have tried added the OutputCache on the LeBlender controller and the Index method, but it didn't seem to have any effect, but it does cache the partials when I add OutputCache on the action methods in my SurfaceController and these are rendered inside the LeBlender grid editor view, e.g.

[OutputCache(Duration = 3600, VaryByParam = "None", VaryByCustom = "Url;User")]
public ActionResult GetProducts([ModelBinder(typeof(IntArrayModelBinder))] int[] ids, int page = 1, int pageSize = 10, SortBy sortBy = SortBy.None, SortDirection sortDirection = SortDirection.Ascending)
{
    int total = 0;
    var products = ProductService.GetProducts(ids, page, pageSize, out total, false, sortBy, sortDirection);

    var pager = new PagerModel(pageSize, page - 1, total);
    var listModel = new ListModel(products, pager);

    return View("/Views/PartialView/ProductList.cshtml", listModel);
}
Html.RenderAction("GetProducts", "ProductList", new { ids = productIds, pageSize = 10 });

and then I have this.

public class Global : UmbracoApplication
{
    public override string GetVaryByCustomString(HttpContext context, string custom)
    {
        var args = custom.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
        var sb = new StringBuilder();

        foreach (var arg in args)
        {
            if (arg.InvariantEquals("Url"))
            {
                var filtered = HttpUtility.ParseQueryString(context.Request.QueryString.ToString());
                var absoluteUri = "url=" + context.Request.Url.AbsoluteUri;

                if (filtered["umbDebug"] != null)
                {
                    filtered.Remove("umbDebug");
                    absoluteUri = "url=" + context.Request.Url.AbsoluteUri.Split('?')[0];

                    if (filtered.Count > 0)
                    {
                        absoluteUri += "?" + filtered;
                    }
                }
                sb.Append(absoluteUri);
                //return absoluteUri;
            }
            else if (arg.InvariantEquals("User"))
            {
                if (context.Request.IsAuthenticated)
                {
                    var user = context.User.Identity.Name;
                    sb.Append("user=" + user);
                    //return "user=" + user;
                }
            }
            //else if (arg.InvariantEquals("UserType"))
            //{
            //    if (context.Request.IsAuthenticated)
            //    {
            //        var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
            //        var member = umbracoHelper.MembershipHelper.GetCurrentMember();
            //        if (member != null)
            //        {
            //            sb.Append("usergroup=" + member.ContentType.Alias);
            //            //return "usergroup=" + member.ContentType.Alias;
            //        }
            //    }
            //}
        }

        if (!string.IsNullOrEmpty(sb.ToString()))
        {
            return sb.ToString();
        }

        return base.GetVaryByCustomString(context, custom);
    }
}

I tried caching by member type, but it seems to create these SQL queries each time, so not sure how to cache an grid editor or partial by member group or member group?

soreng commented 6 years ago

Hi @bjarnef The reason the LeBlender controller output is not affected by the output cache attribute is that the controller is not really used as a "real" controller. It is just there as a method for mapping from LeBlenderModel to your custom model, if needed.

For the caching to work, it should be implemented as you suggest - as it is in macros.

I'm getting ideas for improvements, but they may need a v2 to make them work.