Render block from ContentReference page property - only block name shows

Vote:
 

I am trying to create a page that allows an editor, through on page editing, to drag and drop one, and only one, block onto the page. So in my page type, I have created a property of the block type. In on page editing, I am able to drag and drop the block like I want, but when I go view the page, all that shows is the name of the block instead of the actual block content.

Page Type

    public class OurStoryPage : StandardPage
    {
        [Display(
            Name = "Worldwide Map Block",
            Description = "The page's woldwide map callout block.",
            GroupName = SystemTabNames.Content,
            Order = 501)]
        [AllowedTypes(typeof(ModularContentBlock))]
        public virtual ContentReference WorldwideMapBlock { get; set; }

    }

Page View

@Html.PropertyFor(m => m.CurrentPage.IntroTitle) @Html.PropertyFor(m => m.CurrentPage.IntroText) @{ Html.RenderPartial("_Growth"); } @Html.PropertyFor(m => m.CurrentPage.WorldwideMapBlock

Block View

@model Models.Blocks.ModularContentBlock

@Model.Headline @Model.Body

How do I render the block's view from a ContentReference property?

#146753
Mar 22, 2016 15:54
Vote:
 

The problem is that EPiServer thinks that you want to render a link to that content. And since a block doesn't have a url it will just output the link title. You need to tell EPiServer that you actually want to render the content of the target block. 

I would first extend my viewmodel with the actual block (in my case the EditorialBlock)

public class StartPageContentModel : PageViewModel<StartPage>
{
  public StartPageContentModel(StartPage currentPage)
: base(currentPage)
  {
  }
  public EditorialBlock EditorialBlock { get; set; }
}

In the StartPage contenttype you can have like you have with a content reference (although you might want to consider if a local block will do instead?)

[AllowedTypes(typeof(EditorialBlock))]
public virtual ContentReference EditorialBlockLink { get; set; }



In the page controller I can then use the content loader to load the actual block from the reference

public class StartPageController : PageControllerBase<StartPage>
{
  private readonly IContentLoader _contentLoader;
  public StartPageController(IContentLoader contentLoader)
  {
  _contentLoader = contentLoader;
  }



---------------------

var model = new StartPageContentModel(currentPage);
if (currentPage.EditorialBlockLink != null)
{
  model.EditorialBlock = _contentLoader.Get<EditorialBlock>(currentPage.EditorialBlockLink);
}



and then in my start page view I can render the block

@using EPiServerSiteV9
@model StartPageContentModel

@Html.PropertyFor(x => x.CurrentPage.MainContentArea, new { CssClass = "row equal-height", tag = Global.ContentAreaTags.FullWidth })
@if (Model.EditorialBlock != null)
{
  @Html.PropertyFor(x => x.EditorialBlock)
}



and the Editorial block view is

@model EditorialBlock

<div class="clearfix" @Html.EditAttributes(x => x.MainBody)>
  @Html.DisplayFor(x => Model.MainBody)
</div>

Another option is to create a UIHINT on the property for the ContentReference and a custom display template to render the property for the link and in the display template get the block. But I prefer to keep that logic in controller if possible. In my mind, display templates should grab data from a data source, it should format it.

#146754
Edited, Mar 22, 2016 16:33
Vote:
 

Great answer! Thank you. These blocks will be reused on several pages so I think a global block is appropriate here. In this case, I don't have a controller or a view model that is specific to my page, so I think I'll try creating an HtmlHelper extension to retrieve the Block content given the ContentReference like you've shown in your controller.

I fundamentally agree with you on the concept of the controller providing the content to the view rather than a view going and retrieving the content, but I think the display template approach is, more or less, how EPiServer goes about rendering the blocks when they are placed in a ContentArea already. So maybe it's not a terrible idea in terms of consistency, but if there's already a controller, I'd go with your initial suggestion.

Thanks again. I didn't know if I was missing something.

#146761
Mar 22, 2016 22:38
Vote:
 

There is another way to achieve this, which I usually prefer. Create a display template for "partial" content...

First use a tag when rendering the property:

@Html.PropertyFor(m => m.WorldwideMapBlock, new { Tag = "ContentReferencePartial" })

Then create a template in /shared/DisplayTemplates/ and name i ContentReferencePartial.cshtml (same name as the tag).
Put this in the template:

@using EPiServer.Core


@model ContentReference


@if (!ContentReference.IsNullOrEmpty(Model))
{
    Html.RenderContent(Model);
}

Then you need this HtmlHelper:

public static void RenderContent(this HtmlHelper html, ContentReference contentLink)

{
    var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();

    IContentData contentData;

    if (contentLoader.TryGet(contentLink, out contentData))
    {
        html.RenderContentData(contentData, false);
    }
}
#146769
Mar 23, 2016 13:03
Vote:
 

Oh and of cource you need a view for the block which you've selected in your property WorldWideMapBlock. You probably already have a view for this block.

#146770
Mar 23, 2016 13:05
Vote:
 

This approach work for both MediaData, PageData and BlockData which is nice. Local block/block as property isn't always what we need in the model, sometimes we need to give the editor the possibilty to change between two different blocks.

#146771
Mar 23, 2016 13:14
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* 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.