Error when rending ContentArea property inside a block

Vote:
 

I feel I may be going the wrong way about this, but I have a block that I want to be able to put many images into one place. I defined a property of type ContentArea and restricted it to only allow images. Is this the best/recommended way to allow multiple images? The problem I am running into is trying render the images. If I use either Html.PropertyFor or Html.DisplayFor, both return this error:

The model item passed into the dictionary is of type 'EPiServer.Core.ContentArea', but this dictionary requires a model item of type 'GLHOMES.Website.Features.ShowcaseBlock.ShowcaseBlock'

Do I need to use a view model to load the content referenced in the ContentArea first, and and render them from that? Is this the right approach to support multiple images in a single property? Would IList<ContentReference> work better?

#203510
Apr 25, 2019 21:53
Vote:
 

if you don't need special personalization (aka visitor groups) support for the images - I would go with content reference list instead.

btw, if you just execute `Html.PropertyFor(ContentArea)` -> Episerver will try to render content area and not the images inside. so your approach in any case might require some custom handling

#203512
Apr 25, 2019 22:08
Vote:
 

I agree with Valdis,

If you don't need to personalise the content, then you should use IList<ContentReference> for adding multiple images. But, if you want to use ContentArea (for personalization purpose), then you need a view to be added with same name as of your image media type name. For example, if your image media is ImageFile.cs then add a view file to render image ~/Views/Shared/ImageFile.cshtml.

Add your image tag to render image, for example:

@using EPiServer.Web.Mvc.Html
@model AlloyTraining.Models.Media.ImageFile
<img src="@Url.ContentUrl(Model.ContentLink)" alt="@Model.Description" />

Thanks and regards,

Praful Jangid

#203634
Apr 30, 2019 14:08
Vote:
 

I am still running into this problem. No matter the use case, I cannot use PropertyFor to render a ContentArea inside a block. It seems as though it should work, but it does not. Gives me the error mentioned above about the wrong model type being passed. I would like the option to use personalization if possible, so I would like to get this to work:

"The model item passed into the dictionary is of type 'EPiServer.Core.ContentArea', but this dictionary requires a model item of type 'GLHOMES.Website.Features.ShowcaseBlock.ShowcaseBlock'"

I am using a custom ContentArea renderer to get rid of the <div> arount content items, and I suspect it may be to blame. It seems to fail on calls to RenderContentData(). Below is the code I am using for this custom renderer:

using EPiServer.Core;
using EPiServer.Web;
using EPiServer.Web.Mvc;
using EPiServer.Web.Mvc.Html;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Website.Business.Rendering
{
    public class CustomContentAreaRenderer : ContentAreaRenderer
    {
        private readonly IContentAreaLoader _contentAreaLoader;
        private readonly IContentRenderer _contentRenderer;
        private readonly IContentAreaItemAttributeAssembler _attributeAssembler;
        
        public CustomContentAreaRenderer(
            IContentAreaLoader contentAreaLoader,
            IContentRenderer contentRenderer,
            IContentAreaItemAttributeAssembler attributeAssembler)
        {
            _contentAreaLoader = contentAreaLoader;
            _contentRenderer = contentRenderer;
            _attributeAssembler = attributeAssembler;
        }

        protected override void RenderContentAreaItem(
            HtmlHelper htmlHelper,
            ContentAreaItem contentAreaItem,
            string templateTag,
            string htmlTag,
            string cssClass)
        {
            var renderSettings = new Dictionary<string, object>
            {
                ["childrencustomtagname"] = htmlTag,
                ["childrencssclass"] = cssClass,
                ["tag"] = templateTag
            };

            renderSettings = contentAreaItem.RenderSettings.Concat(
                from r in renderSettings
                where !contentAreaItem.RenderSettings.ContainsKey(r.Key)
                select r).ToDictionary(r => r.Key, r => r.Value);

            htmlHelper.ViewBag.RenderSettings = renderSettings;
            var content = _contentAreaLoader.Get(contentAreaItem);
            if (content == null)
            {
                return;
            }

            bool isInEditMode = IsInEditMode(htmlHelper);

            using (new ContentAreaContext(htmlHelper.ViewContext.RequestContext, content.ContentLink))
            {
                var templateModel = ResolveTemplate(htmlHelper, content, templateTag);
                if (templateModel != null || isInEditMode)
                {
                    bool renderWrappingElement = ShouldRenderWrappingElementForContentAreaItem(htmlHelper);

                    // The code for this method has been c/p from EPiServer.Web.Mvc.Html.ContentAreaRenderer.
                    // The only difference is this if/else block.
                    if (isInEditMode || renderWrappingElement)
                    {
                        var tagBuilder = new TagBuilder(htmlTag);
                        AddNonEmptyCssClass(tagBuilder, cssClass);
                        tagBuilder.MergeAttributes(_attributeAssembler.GetAttributes(contentAreaItem, isInEditMode, templateModel != null));
                        BeforeRenderContentAreaItemStartTag(tagBuilder, contentAreaItem);
                        htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
                        htmlHelper.RenderContentData((IContentData)content, true, templateModel, _contentRenderer);
                        htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.EndTag));
                    }
                    else
                    {
                        // This is the extra code: don't render wrapping elements in view mode
                        htmlHelper.RenderContentData(content, true, templateModel, _contentRenderer);
                    }
                }
            }
        }

        private bool ShouldRenderWrappingElementForContentAreaItem(HtmlHelper htmlHelper)
        {
            // set 'haschildcontainers' to false by default
            bool? item = (bool?)htmlHelper.ViewContext.ViewData["haschildcontainers"];
            return item.HasValue && item.Value;
        }

        protected override bool ShouldRenderWrappingElement(HtmlHelper htmlHelper)
        {
            // set 'hascontainer' to false by default
            bool? item = (bool?)htmlHelper.ViewContext.ViewData["hascontainer"];
            return item.HasValue && item.Value;
        }
    }
}
#204081
May 17, 2019 22:06
Vote:
 

Another thing to add, I disabled my custom renderer to rule out it being the problem. I confirmed that PropertyFor does not work inside blocks for both ContentArea and IList<ContentReference> properties. They both throw the same error. I am at a loss here. I am having to render the items in either of those properties manually using RenderContentData inside a loop, which is not ideal.

#204082
May 17, 2019 22:47
Vote:
 

what is reason having custom content area renderer? to skip potentially wrapping element rendering?

#204086
May 18, 2019 4:33
Vote:
 

Yes. Is there a better way I am not aware of? The default renderer only allows me to change the wrapping element, but not eliminate it when not in edit mode. Either way it turned out not to be my custom renderer at all. I took it out of the mix and it still had issues.

#204141
May 20, 2019 15:57
Vote:
 

it's not like I'm delighted to do a self promotion here, but you can try out Bootstrap area package. It does what you need and even more. Reads docs. And don't be afraid of the name, it has almost nothing in common with bootstrap framework nowadays. it's just historical name

#204143
May 20, 2019 16:16