0

This has to be pretty simple to do, but I'm not finding a whole lot of examples on it. I am wanting to get all posts with their corresponding tags and display in a view. Basically a simple blog type thing. Here is my code with Models, ViewModel, Controller and View. Everything works minus the tags, so something is obviously wrong with my syntax.

public class MeWallPost
{
    public virtual int MeWallPostId { get; set; }

    public virtual string PostTitle { get; set; }        
    public virtual string PostContent { get; set; 
    public virtual DateTime PublishDate { get; set; }

    public virtual ICollection<MeWallPostMeTag> MeWallPostMeTags { get; set; }
}

public class MeTag
{
    public virtual int MeTagId { get; set; }

    public virtual string Name { get; set; }

    public virtual ICollection<MeWallPostMeTag> MeWallPostMeTags { get; set; }
}

public class MeWallPostMeTag
{
    [Key, Column(Order = 0)]
    public virtual int MeWallPostId { get; set; }
    [Key, Column(Order = 0)]
    public virtual int MeTagId { get; set; }

    public virtual MeWallPost MeWallPost { get; set; }
    public virtual MeTag MeTag { get; set; }
}

----

public class MeWallPostViewModel
{
    public string PostTitle { get; set; }
    public string PostContent { get; set; }
    public DateTime PublishDate { get; set; }

    private IEnumerable<MeTag> _Tags = new List<MeTag>();
    public IEnumerable<MeTag> Tags
    {
        get { return _Tags; }
        set { _Tags = value; }
    }

    public IEnumerable<MeWallPostViewModel> GetPosts()
    {
        var db = new DatselleDB();

        return from post in db.MeWallPosts             
               select new MeWallPostViewModel
               {
                   PostTitle = post.PostTitle,
                   PostContent = post.PostContent,
                   PublishDate = post.PublishDate,
                   Tags = post.MeWallPostMeTags.Select(a => a.MeTag)
               };
    }
}

-----

public ViewResult Index()
{
   return View(new MeWallPostViewModel());
}

------

@foreach (var item in Model.GetPosts()) {
<div class="postItem clear">
    <h3>@Html.DisplayFor(modelitem => item.PostTitle)</h3>
    <span class="date">@Html.DisplayFor(modelitem => item.PublishDate)</span>
    <p>@Html.DisplayFor(modelItem => item.PostContent)</p> 
    @foreach (var tag in Model.Tags) {
        @Html.DisplayFor(modelItem => tag.Name.ToString())
    }
</div> 
}
stackDat
  • 101
  • 7

2 Answers2

1

Raphaël Althaus is right about your viewmodel and controller code. Try this:

ViewModel:

public class MeWallPostViewModel
{
    public string PostTitle { get; set; }
    public string PostContent { get; set; }
    public DateTime PublishDate { get; set; }

    public MeTagViewModel[] Tags { get; set; }

    public class MeTagViewModel
    {
        public int MeTagId { get; set; }
        public string Name { get; set; }
    }
}

Controller:

public ViewResult Index()
{
    MeWallPostViewModel[] viewModels = null;
    using (var db = new DatselleDB())
    {
        viewModels = db.MeWallPosts
           .Include(p => p.MeWallPostMeTags.Select(t => t.MeTag))           
           .Select(p => new MeWallPostViewModel
           {
               PostTitle = p.PostTitle,
               PostContent = p.PostContent,
               PublishDate = p.PublishDate,
               Tags = p.MeWallPostMeTags.Select(t => 
                   new MeWallPostViewModel.MeTagViewModel
                   {
                       MeTagId = t.MeTagId,
                       Name = t.Name,
                   })
                   .ToArray()
               ),
           })
           .ToArray();
    }
    return View(viewModels);
}

View:

@model IEnumerable<MeWallPostViewModel>

@foreach (var item in Model)
{
    <div class="postItem clear">
        <h3>@Html.DisplayFor(m => item.PostTitle)</h3>
        <span class="date">@Html.DisplayFor(m => item.PublishDate)</span>
        <p>@Html.DisplayFor(m => item.PostContent)</p> 
        foreach (var tag in item.Tags)
        {
            @Html.DisplayFor(m => tag.Name)
        }
    </div> 
}

Another thought:

Use AutoMapper to clean up the controller's Select and ToArray code.

Reply to comments

To your first question, read this. Basically, don't convert it to a list unless you need to add or remove items later. In the case of converting entities to viewmodels, you should rarely need to add or remove items after querying out from the db.

To your second question, see my afterthought. Using AutoMapper, your controller code could end up looking like this:

public ViewResult Index()
    {
        // same # of lines as your original GetPosts (not counting this comment)
        MeWallPost[] entities = null;
        using (var db = new DatselleDB())
        {
            entities = db.MeWallPosts
               .Include(p => p.MeWallPostMeTags.Select(t => t.MeTag))
               .ToArray();
        }

        // technically, CreateMap should not be in the controller, but instead
        // bootstrapped once from the composition root (global.asax)
        Mapper.CreateMap<MeTag, MeWallPostViewModel.MeTagViewModel>();
        Mapper.CreateMap<MeWallPost, MeWallPostViewModel>()
            .ForMember(d => d.Tags, o => o.ResolveUsing(s => 
                Mapper.Map<MeWallPostViewModel.MeTagViewModel[]>
                    (s.MeWallPostMeTags.Select(a => a.MeTag))))
        ;

        var viewModels = Mapper.Map<MeWallPostViewModel[]>(entities);
        return View(viewModels);
    }

Also in your question you had an entity mixed in with a viewmodel:

public class MeWallPostViewModel
{
    private IEnumerable<MeTag> _Tags = new List<MeTag>();
    public IEnumerable<MeTag> Tags
    {
        get { return _Tags; }
        set { _Tags = value; }
    }

Each time you DTO from an entity to a viewmodel, you should implement separate viewmodels for the original entity's navigation and (in your case) collection properties that you want to include in the view.

Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Is there an advantage to using ToArray vs ToList? I was basing my code off if Neil T's answer on this post http://stackoverflow.com/questions/2145034/modeling-a-many-to-many-relationship-in-asp-net-mvc-using-linq-to-sql, but attempting to pull all records instead of one. I haven't gotten a chance to test this but it just seems like a lot of added code. Maybe I'm not grasping the necessity. Thank you for your help. – stackDat Apr 12 '12 at 19:01
  • 1
    The other answer you linked to in your comment is more than 2 years old, uses LINQ to SQL, and MVC1. Also, Neil T had the data query in his controller, not in the viewmodel. – danludwig Apr 12 '12 at 20:10
  • Thank you for your help. I am getting some syntax errors in the non-mapper controller code. I will look into those and try out AutoMapper later tonight. Thanks Again. – stackDat Apr 12 '12 at 22:58
  • @stackDat yes I didn't check to make sure it would compile. However, the idea is correct. Use .Include to eager load the collection, then map it to your viewmodel. – danludwig Apr 12 '12 at 23:18
  • I looked into AutoMapper, implemented it and added the line Mapper.CreateMap(); to the code above, that you supplied. Unfortunately Tags are null when the page renders. Is there something missing from the entities query? When I step through I can see that MeWallPostTags has the correct count in relation to the tags. Any ideas? – stackDat Apr 13 '12 at 22:39
  • This question is turning into an answer about AutoMapper, which doesn't really fit with the original question. However I have accepted your edits and added some of my own. You need at least 2 CreateMap calls -- 1 to map from MeWallPost to MeWallPostViewModel, and another to map from MeTag to MeWallPostViewModel.MeTagViewModel. You then need a .ForMember call in the map to convert the entity collection to the viewmodel collection. Ask a different question if you need more clarification on how to map these entities to their respective view models. – danludwig Apr 13 '12 at 23:12
  • Thank you, this works perfectly now. Finding examples of this has been difficult. I realize the code needs to be cleaned up. I was going to worry about that after I got it working. – stackDat Apr 13 '12 at 23:28
0

Edit : sorry.

Your code is a little bit weird.

  • don't put db in your viewmodel
  • your GetPosts method make no sense as is (nothing to do with the class and it's properties... why ?)

I would do at least (far from good, but already better)

put your GetPosts() method in your Controller, as private (this is not the best way to do, but this will be better).

I hope your db implements Disposable, so use it with a using

then

public ViewResult Index() {
   return View(GetPosts())
}

your View Index should have @model IEnumerable

and then in your view

@foreach (var post in Model) {
 bla bla
}

EDIT 2: By the way, try to debug your query to see if it's return anything

Raphaël Althaus
  • 59,727
  • 6
  • 96
  • 122
  • Sorry, that was only entered as a test, it should have been removed in my question. Tags still don't display. – stackDat Apr 12 '12 at 16:09