MVC4 Razor DisplayTemplate called, HTML generated,

2019-07-07 09:38发布

问题:

I've got a view that iterates a collection and calls DisplayFor() for each element in the collection.

I need to manually iterate (as opposed to passing the collection to DisplayFor) in order to tell the template if a break in the list should be drawn. The items in the list will only be of 2 types, ordered by them, so I only need to show this break once.

My template is found and called correctly.
I can see the HTML it generates correctly, ie: DisplayFor().ToHtmlString()
I can set this HTML as a scoped variable, ie: var html = DisplayFor().ToHtmlString() ..
But even Html.Raw(html) does not render it in the browser - the HTML has simply vanished.

What's going on?

var renderBreakInList = Model.Items.Any(x => x.IsSomeType);
foreach(var item in Model.Items)
{
    var renderBreak = renderBreakInList && item.IsOtherType;
    Html.DisplayFor(x => item, new { renderBreak = renderBreak });

    if (renderBreak)
    {
        renderBreakInList = false;
    }
}

回答1:

The Html.DisplayFor method in itself does not render anything to the response just returns the generated HTML as a MvcHtmlString.

In order to actually write the rendered HTML to the response you need to tell this to Razor with using the @ sign:

@Html.DisplayFor(x => item, new { renderBreak = renderBreak })

So your whole code should look like this:

@{
    var renderBreakInList = Model.Items.Any(x => x.IsSomeType);
    foreach(var item in Model.Items)
    {
        var renderBreak = renderBreakInList && item.IsOtherType;
        @Html.DisplayFor(x => item, new { renderBreak = renderBreak })

        if (renderBreak)
        {
            renderBreakInList = false;
        }
    }
}

Or you can use the WebPageBase.Write method (which gets called under the hood when using the @ sign):

Write(Html.DisplayFor(x => item, new { renderBreak = renderBreak }));


回答2:

finally figured this out after trying a lot of different things and reworking how I tell the Template to draw the break.

Rather than send a bool which I'd prefer to make the template more robust (if the order changes), I'm passing in the ID of the item that should draw the break.

@{ 
    var breakItem = Model.Items.FirstOrDefault(x => renderBreakInList && x.IsSomeType);
    var breakID = breakItem == null ? (long?)null : (long)breakItem.ID;
}

@Html.DisplayFor(x => x.Items, new { breakID = breakID })    

Also like nemesv pointed out, Html.DisplayFor() needs to be prepended with @. I got out of the habit of doing this inside code blocks because I would always get the 'once inside a code block you don't need @` error..