Optimizely 12+ with Forms : Custom form container, FormContainerBlock Partial not found

Vote:
 

Hi

Just wanted to share something I found that might help someone else going forward or perhaps someone knows a better way of doing it. 

Im running Optimizely CMS 12+ with Razor Pages and the new "/Features" structure from Foundation  and was experiencing routing issues with the 

FormcontainerBlock partial or specific Elementblocks after installing Optimizely Forms. 

I could render the built in element types in the default FormContainer but when creating a custom FormContainer Block the application couldnt find the FormContainerBlock partial:

@await Html.PartialAsync("FormContainerBlock", Model) - Resulted in partial not found error

To fix this I had to add path: ~/FormsViews/Views/ElementBlocks/Components/{0}/{0}.cshtml to my ViewExpander since the FormContainerBlock.cshtml partial is now in below "Views/ElementBlocks/Components/FormContainerBlock/" in the Episerver.Forms module

Foundation view expander example (running MVC): https://github.com/episerver/Foundation/blob/main/src/Foundation/Infrastructure/Display/FeatureViewLocationExpander.cs 

Example:

Startup.cs

  services.AddRazorPages(opt => opt.RootDirectory = "/Features")
                .AddRazorOptions(ro => {
                    ro.ViewLocationExpanders.Add(new FormsViewLocationExpander());
                });

FormsViewLocationExpander.cs

 public class FormsViewLocationExpander : IViewLocationExpander
    {
        private readonly List<string> _viewLocationFormats = new List<string>()
        {
            "~/FormsViews/Views/ElementBlocks/{0}.cshtml",
            "~/FormsViews/Views/ElementBlocks/Components/{0}/{0}.cshtml",
        };
 
        public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
            IEnumerable<string> viewLocations)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
 
            if (viewLocations == null)
            {
                throw new ArgumentNullException(nameof(viewLocations));
            }
 
            var expandedLocations = viewLocations.Union(_viewLocationFormats);
            foreach (var item in expandedLocations)
            {
                yield return item;
            }
        }
 
        public void PopulateValues(ViewLocationExpanderContext context)
        {
            return;
        }
    }
#270594
Edited, Jan 26, 2022 14:47
Palle Mertz - Jan 31, 2022 11:10
Damn that helped me alot even though I am not using foundation and even viewlocation expander (imho view components shouldn't rely on magic) ...
Needed the view path for the FormContainerBlock.cshtml razor view for my custom formcontainer :D
Vote:
 

Just a heads-up, there have been a lot of updates to the NET5 version of Foundation in the past week, including some Forms-related updates. Note that it's now using the "main" branch for the official CMS 12 version (no longer using the net5 branch).

#270738
Jan 27, 2022 23:17
Vote:
 

Great saw that! 

I can see Foundation now have the views in Features/Shared/Views/ElementBlocks.

Just a curious question, is there a reason (that would be good to know) why Foundation have copied the element/forms views there instead of using the episerver form routes to the views in the forms module? Since I cant see that you have changed anything in the views. 

Couldnt you have looked in the Features shared folder first and then fallback to FormsViews? https://github.com/episerver/Foundation/blob/main/src/Foundation/Infrastructure/Display/FeatureViewLocationExpander.cs 

"/Features/Shared/Views/ElementBlocks/{0}.cshtml",

"~/FormsViews/Views/ElementBlocks/{0}.cshtml",

"~/FormsViews/Views/ElementBlocks/Components/{0}/{0}.cshtml",
#270745
Jan 28, 2022 7:34
Vote:
 

We added custom classes to the forms so they are not the same.  Also for consistency we put them in the Features/Shared

#270824
Jan 29, 2022 1:47
Vote:
 

How does your CustomFormContainerBlockController look? I'm seeing some issues in migrating a custom form container from CMS 11 to CMS 12.  

Previously it was inheriting from FormContainerBlockController. Should it inherit from BlockComponent<T> now?

Is there an updated version of this page from the developer docs?

docs.developers.optimizely.com/content-cloud/v1.2.0-forms/docs/creating-a-custom-form-block

#284039
Jul 20, 2022 23:48
Vote:
 

Hehe ye that specific developer docs page is not updated for CMS12+ / Forms 5+

You should still inherit from FormContainerBlockController but even if they kept the name the did change the type. FormContainerBlockController now inherits from BlockComponent<FormContainerBlock>.

Here is what our InvokeComponents looks like:

        protected override IViewComponentResult InvokeComponent(
            FormContainerBlock currentBlock)
        {
            //override viewpath
            var baseResult = base.InvokeComponent(currentBlock) as ViewViewComponentResult;
            baseResult.ViewName = "~/Views/FormContainerBlock/FormContainerBlock.cshtml";

            // ... other logic ... example adding stuff to ViewBag which can be used in the custom razor view

            return baseResult;
        }
#284044
Jul 21, 2022 9:47
Vote:
 

We use a custom built forms container that looks like this:

[TemplateDescriptor(AvailableWithoutTag = true,
                ModelType = typeof(CustomFormContainer),
                TemplateTypeCategory = TemplateTypeCategories.MvcPartialComponent)]
public class CustomFormContainerController : FormContainerBlockController
{
    protected override IViewComponentResult InvokeComponent(FormContainerBlock currentBlock)
    {
        // Custom code...

        return base.InvokeComponent(currentBlock);
    }
}

And I get the following error when I visit a page with our custom block:
InvalidOperationException: The view 'Components/CustomFormContainer/FormContainerBlock' was not found. 

In the examples above it seems like this error is resolved by coping Optimizley's FormContainerBlock.cshtml from the zip file located under "/modules" to "Views/Shared/Components/CustomFormContainer/FormContainerBlock.cshtml".
Optimizly's documentation says the same thing: https://docs.developers.optimizely.com/content-cloud/v1.2.0-forms/docs/creating-a-custom-form-block (unfortunately not updated for CMS 12).

As far as I can see we didn't have to do that on our old project that runs CMS 11.

Is this the only way to render the view if you want a custom forms container?
What I don't like about that solution is that we have to keep the copied file updated when ever we update Optimizley Forms.

All suggestions are very welcome. :)

#284462
Jul 26, 2022 15:18
Vote:
 

We were using a custom built forms container in CMS 11 and we did have to do what's described in the documentation; make required changes to the ascx file and copy to Views/Shared/ElementBlocks folder.

I'm assuming this is how it's done in CMS12 also, but with the razor file instead of the web forms file. 

Now in CMS12, my controller looks like yours Lucas. It's now working when in edit mode, but I get a runtime error when viewing the page as an end-user. 

RuntimeBinderException: Cannot convert null to 'bool' because it is a non-nullable value type

on line 165 in CustomFormsContainerBlock.cshtml    var validationCssClass = ViewBag.ValidationFail ? "ValidationFail" : "ValidationSuccess";

#284501
Jul 26, 2022 23:05
Vote:
 

 I realized what I was missing when looking at this problem on a new day with a fresh mind.

Camilla used Html.PartialAsync to render Optimizley's forms container view.
So instead of coping Optimizley's view from the zip folder I ended up with a view that looks like this:

@model EPiServer.Forms.Implementation.Elements.FormContainerBlock

@await Html.PartialAsync("FormContainerBlock", Model)

Note that the view still has to be located here: "Views/Shared/Components/CustomFormContainer/FormContainerBlock.cshtml", and you need to use the "FormsViewLocationExpander" described above.
I'm happy with this solution since we don't have to keep our file in sync whenever Optimiley updates the nuget package.

Larry - So far I'm not experiencing the same problem your describing. Perhaps your custom forms container code does something to the ViewBag? You can also try to add a custom forms container to Alloy to ensure it's not something in your code that's causing the problem.



#284510
Jul 27, 2022 7:11
* 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.