2

First I have a Display Template in Views > Blog > DisplayTemplates:

@model Domain.Model.BlogPost
<div class="blogSummary">
    @Html.ActionLink(Model.Title, "post", new { Id = Model.PostID }, new { @class = "titleLink" })
    <div class="subInfo">
        Posted by @Model.Author on @Model.PostedDate.ToString("D") | @Model.PostComments.Count
        Comment(s)</div>
    <br />
    <div class="summaryText">
        @Html.Markdown(Model.Body.Length > 300 ? Model.Body.Remove(0, 300) + "..." : Model.Body)
        @Html.ActionLink("(view more)", "post", new { Id = Model.PostID })
    </div>
</div>

Then I have a view:

@model IEnumerable<Domain.Model.BlogPost>
@{
    ViewBag.Title = "Index";
}
@Html.DisplayFor(x => x, "BlogPostSummary")

However, I am getting an error on the DisplayFor():

The model item passed into the dictionary is of type 'System.Data.Objects.ObjectSet`1[Domain.Model.BlogPost]', but this dictionary requires a model item of type 'Domain.Model.BlogPost'.

I'm having a bit of trouble understanding what's going on. What is the best way to use the display template?

CatDadCode
  • 58,507
  • 61
  • 212
  • 318

4 Answers4

4

The problem is you're trying to pass in a collection of BlogPosts, when a single BlogPost is expected.

Try:

@Html.DisplayFor(x => x[0], "BlogPostSummary")

Or something like:

@foreach (var post in Model)
    @Html.Display(post)

Note: I'm not convinced the second example is using Html.Display correctly. My suspicion is that the code would be better served using a partial instead of a display template.

UIHint

By default, ASP.NET MVC relies on naming conventions to link BlogPost the class to BlogPost.cshtml the display template. If however, you would like to use CustomBlogPost.cshtml as your display template, you can do so by applying the UIHint attribute.

public class DomainModel {
    [UIHint("CustomBlogPost")]
    public BlogPost Post { get; set; }
} 
kim3er
  • 6,306
  • 4
  • 41
  • 69
  • 1
    Interesting. In another app I have, I did this `@Html.EditorFor(x => x.Guests)` and it worked just fine. The model of the view was of type `Invite` which had a navigation property `Guests` of type `EntityCollection`. I was not iterating over the collection; it did it automatically :/ – CatDadCode Mar 16 '11 at 19:48
  • My feeling is on this one though, that you should be using a partial instead of a display template. Is there any reason why you haven't? – kim3er Mar 16 '11 at 19:57
  • No reason, I was just told in another question somewhere to avoid looping through collections in my views and to use templates. See the accepted answer here: http://stackoverflow.com/questions/5070399/how-to-use-multiple-form-elements-in-asp-net-mvc That's where he told me to use editors and display templates. – CatDadCode Mar 16 '11 at 20:03
  • I wasn't aware of that functionality, but it doesn't seem to be applicable to display templates. – kim3er Mar 16 '11 at 20:09
  • It is. I just figured it out. It's because of the naming convention. When I renamed my display template to BlogPost.cshmtl it all started working. This just sucks because I don't want that to be the default display template. – CatDadCode Mar 16 '11 at 20:20
  • This is getting too tedious. I am just going to use partial views as you said. Thank you. – CatDadCode Mar 16 '11 at 20:27
  • Yep, the template needs to be named the same as the intended model. This can be overridden with domain models using the `UIHint` attribute though. – kim3er Mar 16 '11 at 20:30
  • Do you have a link to the source of that info? – CatDadCode Mar 16 '11 at 20:31
  • `UIHint` is contained within the Data Annotations namespace. I've provided a brief summary in my answer, but more info can be found at http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.uihintattribute.uihint.aspx – kim3er Mar 16 '11 at 20:41
  • Ah, I see. Same issue though. It assumes I only ever want to use ONE template. Doesn't give me the option to specify several templates. I'll stick with partial view for now. Thank you again. – CatDadCode Mar 16 '11 at 20:47
2

Try this maybe

@Html.DisplayFor(x => x.First(), "BlogPostSummary")
jessegavin
  • 74,067
  • 28
  • 136
  • 164
2

I've been working on the same problem, and I think I have a better solution for you. You can loop through the collection and still get this to work:

@foreach(var blogPost in Model)
{
    Html.DisplayFor(x => blogPost, "BlogPostSummary")
}

Using a variable on the left side of a lambda expression doesn't necessarily require you to also use it on the right side. DisplayFor expects an Expression<Func<TModel, TValue>>. TModel is the type of your model, such as IEnumerable<BlogPost>, but TValue can be anything you like. So as long as you have a reference to the current item in your collection, as in the foreach above, you can ignore the left side of the lambda expression.

(But you do have to have the TModel. Html.DisplayFor(() => blogPost, "BlogPostSummary") doesn't work.)

Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
  • Right I was aware I could loop in this manner (though it seems hackish and probably better suited to partial views). My issue was that I loved how the display template automatically detected that it was being passed a collection of it's model type and did the looping/repeating implicitly. Unfortunately, this only works if you use the proper naming convention for templates. Which means only one template per type, and I needed more than one template per type. Thanks for the answer though! – CatDadCode Mar 24 '11 at 20:03
  • 1
    I'm actually doing something like this right now, and the above syntax, while it seems like it *should* work, will not. You will be able to step into your Template, but the generated html will *never* get rendered. You have to prepend `Html.DisplayFor()` with `@`. Typically this would get you the `once in a code block you don't need to prepend statements with '@'` error, but apparently there are some special rules.. http://stackoverflow.com/questions/17412113/mvc4-razor-displaytemplate-called-html-generated-but-not-rendered-to-browser – JoeBrockhaus Jul 01 '13 at 20:13
0

Basically it's because doing an @Html.DisplayFor() implicitly tells MVC that the Model to pass on is the Model of the passing View, i.e. IEnumerable<Domain.Model.BlogPost>

What you need to do is enumerate over the Model, and display each of them:

@foreach(var blogPost in Model){

    Html.RenderPartial("BlogPostSummary", blogPost)

}

Make sure the BlogPostSummary is in the correct folder, and you should be good to go!

Yngve B-Nilsen
  • 9,606
  • 2
  • 36
  • 50