Require client resources only when specific block type is rendered

Ted
Ted
Vote:
 

I want specific client resources to be loaded only when a specific block type is rendered on a page, but I'm not sure how to do it in CMS 12.

I was under the impression that client resources can be required at any time, even within Razor views/pages using something like:

@{
    EPiServer.Framework.Web.Resources.ClientResources.RequireScript("someScriptPathOrResource").AtFooter();
}

I've also tried adding it to the ViewComponent type like so:

public class MyBlockViewComponent : ViewComponent
{
    public MyBlockViewComponent(IRequiredClientResourceList requiredClientResourceList)
    {
        requiredClientResourceList.RequireScript("/scripts/some-script.js, "someName", new[] { "someDependency" }).AtFooter();
    }

    async public Task<IViewComponentResult> InvokeAsync(MyBlock myBlock)
    {
        // Return view
    }
}

However, the script isn't added, despite the code above being executed.

The layout file contains the following:

@Html.RequiredClientResources("Footer")

It does work if I require the script through a class implementing IClientResourceRegistrator, but then the script always gets rendered, instead of only when a block of a specific type is rendered on the page.

#271287
Edited, Feb 07, 2022 10:47
Vote:
 

I can see that in Foundation for .NET 5 they have usage through .Require

using EPiServer.Framework.Web.Resources;
using EPiServer.Web.Mvc;
using Foundation.Features.Shared;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Foundation.Features.Blocks.Healthbot
{
    public class HealthChatBotBlockController : AsyncBlockComponent<HealthChatbotBlock>
    {
        private readonly IRequiredClientResourceList _requiredClientResourceList;

        public HealthChatBotBlockController(IRequiredClientResourceList requiredClientResourceList)
        {
            _requiredClientResourceList = requiredClientResourceList;
        }

        protected override async Task<IViewComponentResult> InvokeComponentAsync(HealthChatbotBlock currentBlock)
        {
            _requiredClientResourceList.Require(HealthBotClientResourceProvider.BotJs).AtHeader();
            var model = new BlockViewModel<HealthChatbotBlock>(currentBlock);
            return await Task.FromResult(View("/Features/Blocks/HealthBot/HealthChatBotBlock.cshtml", model));
        }
    }

    [ClientResourceProvider]
    public class HealthBotClientResourceProvider : IClientResourceProvider
    {
        public static string BotJs = "healthbot.webchat";

        public IEnumerable<ClientResource> GetClientResources()
        {
            return new[]
            {
                new ClientResource
                {
                    Name = BotJs,
                    ResourceType = ClientResourceType.Html,
                    InlineContent = @"<script crossorigin=""anonymous"" src=""https://cdn.botframework.com/botframework-webchat/latest/webchat.js""></script>"
                }
            };
        }
    }
}
#271288
Feb 07, 2022 10:53
Ted
Vote:
 

Thanks for helping, Scott!

No luck, though.

I tried with a separate IClientResourceProvider, but requiring the script in InvokeComponentAsync doesn't result in the script being loaded.

However, if I add IClientResourceRegistrator, the script gets loaded - but that defeats the purpose since it results in the script being loaded regardless of whether my block type is rendered on the page.

[ClientResourceProvider]
public class MyResourceProvider : IClientResourceProvider
{
    public static string ResourceName = "someResource";

    public IEnumerable<ClientResource> GetClientResources()
    {
        return new[]
        {
            new ClientResource
            {
                Name = ResourceName,
                ResourceType = ClientResourceType.Script,
                Dependencies = new List<string> { "someDependency" },
                Path = "https://temp.uri/somescript.js"
            }
        };
    }
}

[ClientResourceRegistrator]
public class ClientResourceRegister : IClientResourceRegistrator
{
    public void RegisterResources(IRequiredClientResourceList requiredResources)
    {
        // Always results in the script being loaded (not just when a ContactFormBlock is rendered on the page)
        requiredResources.Require(MyResourceProvider.ResourceName);
    }
}

public class ContactFormBlockViewComponent : AsyncBlockComponent<ContactFormBlock>
{
    private readonly IRequiredClientResourceList _requiredClientResourceList;

    public ContactFormBlockViewComponent(IRequiredClientResourceList requiredClientResourceList)
    {
        _requiredClientResourceList = requiredClientResourceList;
    }

    async protected override Task<IViewComponentResult> InvokeComponentAsync(ContactFormBlock currentContent)
    {
        // Does not result in script being loaded
        _requiredClientResourceList.RequireScript(MyResourceProvider.ResourceName);

        return await Task.FromResult(
            View("~/Features/ContactForm/ContactForm.cshtml", currentContent)
        );
    }
}
#271291
Feb 07, 2022 11:50
Vote:
 

@Ted, as far as I know, I don't think you can register client resources from a Component.

You can do it from a controller:

_requiredClientResourceList.Require("fancy_script")
                         .AtHeader();
#271292
Edited, Feb 07, 2022 12:10
Scott Reed - Feb 07, 2022 12:12
In .NET 5 the blocks are all view components and the big point of this method was to allow resources only to load in when the block was added in the page. So that's another step back if it's only working for controllers. It's just like OutputCache to me as that's now worse that it was due to components
- Feb 07, 2022 12:16
Don't shoot the messenger! ;) At least we did not manage to get it working. I am hoping someone from Optimizely could tell us wrong.
Scott Reed - Feb 07, 2022 12:18
hahaha, yes it's not your fault. I think you'll be right, just seems like view components are a real annoyance with some of the areas we were so used to working with. I used to love some good donut hole caching but all the code that works now doesn't lol
Ted - Feb 07, 2022 13:22
Thanks Mari, appreciate your help, but still no luck.
The only way I get the script to render is to either 1) require it through a type implementing IClientResourceRegistrator or 2) require it from with a layout view directly.
Requiring the script from a controller, view component type, Razor page, or partial view, fails.
Ted
Vote:
 

Failed to get this to work, and instead went with a rather hacky solution that hurts my eyes...

In a nutshell: the Razor page model determines if the page includes blocks that require specific client resources.

public class OnePagerModel : RazorPageModel<OnePager>
{
    private readonly IRequiredClientResourceList _requiredClientResourceList;

    public OnePagerModel(IRequiredClientResourceList requiredClientResourceList)
    {
        _requiredClientResourceList = requiredClientResourceList;
    }

    public void OnGet()
    {
        // Iterate over blocks in a content area to see if there are any blocks that require specific client resources

        var contentAreaContents = CurrentContent.MyContentArea.FilteredItems.Select(x => x.GetContent());

        if (contentAreaContents.Any(x => x is ContactFormBlock))
        {
            _requiredClientResourceList.Require("contactFormBlock").StylesOnly().AtHeader();
            _requiredClientResourceList.Require("contactFormBlock").ScriptsOnly().AtFooter();
        }
    }
}
#271297
Edited, Feb 07, 2022 14:08
Vote:
 

This is a known issue. Requiring resources from a partial view/compontent has stopped working in CMS12. The fix will be released in 12.4.0 https://world.optimizely.com/support/Bug-list/bug/CMS-21308.

#271349
Feb 08, 2022 9:17
Ted - Mar 03, 2022 7:11
Thanks, Johan!
Camilla - Mar 03, 2022 15:21
Seems to be affecting episerver.forms as well, running site on episerver.cms 12.3.1 and episerver.forms js & css resources are no longer added to header & footer, worked on 12.1. When do you think new version of episerver.cms package will be out with upgraded dependencies so they references episerver.cms.core 12.4 instead?
Johan Petersson - Mar 03, 2022 15:26
It was released Feb 22.
Camilla - Mar 03, 2022 16:16
yeah episerver.cms.core, was asking if there's going to be and upgrade to the "Episerver.CMS" package as well, which was latest upgraded on 15 feb. If I install a clean template website with latest "Episerver.CMS" (https://nuget.optimizely.com/package/?id=EPiServer.CMS) package which was released feb 15, you get episerver.cms.core 12.3 through its dependencies. In 12.3.1 episerver.cms, formcontainerblocks isnt getting its required js & css resources which I assume is cuz of this bug or some other bug in episerver.cms since it works fine on 12.1.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.