Entity vs ViewModel question

I was just wondering if there were any reason not to pass an entity (or list of them) into your views (especially with simple L2S classes)?

All the examples I’ve seen, from the things at asp.net/mvc (including NerdDinner), all the way up to the DDD sample app from SharpArch, they all pass entities in, using ViewModels only when really needed.

Other than the risk of accidentally updating something (considering they are still being tracked by your context and / or session), I don’t see any real benefit in nearly duplicating these entities as simple dto’s.

Or is that one risk enough not to?

Opinions?

It depends. If there’s a real chance to accidentally mess up your database, then I would probably advise against it. I mean, how many MS tutorials demonstrate a SqlDataSource? Would you EVER use one? The samples aren’t always a good guideline unfortunately.

However, if its just a matter of making sure that you don’t update something yourself, then its probably OK. Developer discipline is not a great plan, but if the alternative having to overbuild the app, then its probably a decent tradeoff.

I was just curious what others thought about it. Truth is, I can see several reasons not to. The least of which is when using nhibernate style entities, with a non-default constructor, model binding just can’t work. Not to mention all those Bind exlude and include attributes.

As it is, I’ve been going over the various methodologies on it today, and have come to a personal solution, albeit seemingly more work.

I had been using a MappingService and ValidationService to map and validate my view models but they were growing quite large (even for me). As for why I want to map things myself, I have a lot of pre-formatting to do on certain things that AutoMapper isn’t really setup to handle. The larger they grew, the more I wanted to dump them all.

My solution was to integrate self-mapping and self-validation into the view models themselves, giving me not only rich domain entities, but rich view models as well.

Below are some examples:

Used in the Manager.aspx view:

using System.Collections.Generic;
namespace Venue.Application.Models.AdminCP
{
    using Venue.Domain;
    public class ContentManagerModel
    {
        public IEnumerable<ContentManagerCategoryBitModel> CategoryBits { get; set; }
        public static ContentManagerModel Build(IEnumerable<Category> entities)
        {
            return new ContentManagerModel
            {
                CategoryBits = ContentManagerCategoryBitModel.Build(entities)
            };
        }
    }
}

Used in the Manager_CategoryBit.ascx control:

using System.Collections.Generic;
using System.Linq;
namespace Venue.Application.Models.AdminCP
{
    using Venue.Domain;
    public class ContentManagerCategoryBitModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public IEnumerable<ContentManagerForumBitModel> ForumBits { get; set; }
        public static IEnumerable<ContentManagerCategoryBitModel> Build(IEnumerable<Category> entities)
        {
            foreach (Category entity in entities.OrderBy(x => x.Sequence))
                yield return ContentManagerCategoryBitModel.Build(entity);
        }
        public static ContentManagerCategoryBitModel Build(Category entity)
        {
            return new ContentManagerCategoryBitModel
            {
                Id = entity.Id,
                Title = entity.Title,
                ForumBits = ContentManagerForumBitModel.Build(entity.Forums)
            };
        }
    }
}

Used in the Manager_ForumBit.ascx control:

using System.Collections.Generic;
using System.Linq;
namespace Venue.Application.Models.AdminCP
{
    using Venue.Domain;
    public class ContentManagerForumBitModel
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public static IEnumerable<ContentManagerForumBitModel> Build(IEnumerable<Forum> entities)
        {
            foreach (Forum entity in entities.OrderBy(x => x.Sequence))
                yield return ContentManagerForumBitModel.Build(entity);
        }
        public static ContentManagerForumBitModel Build(Forum entity)
        {
            return new ContentManagerForumBitModel
            {
                Id = entity.Id,
                Title = entity.Title
            };
        }
    }
}

Used in the Edit.aspx view:

using System.Collections.Generic;
namespace Venue.Application.Models.AdminCP
{
    using FluentEngine.Application;
    using Venue.Application.Resources;
    using Venue.Application.Specifications;
    using Venue.Domain;
    public class CategoryEditModel
    {
        public int Id { get; set; }
        public int Sequence { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public bool Visible { get; set; }
        public IEnumerable<IValidationError> Validate()
        {
            if (new IsNullEmptyOrWhiteSpace().IsSatisfiedBy(Title))
                yield return new ValidationError("title", Locale.validation_error);
            if (new IsNullEmptyOrWhiteSpace().IsSatisfiedBy(Description))
                Description = string.Empty;
        }
        public static CategoryEditModel Build(Category entity)
        {
            return new CategoryEditModel
            {
                Id = entity.Id,
                Sequence = entity.Sequence,
                Title = entity.Title,
                Description = entity.Description,
                Visible = entity.Visible 
            };
        }
    }
}

My base controller has a method called ConsumeErrors that merges the return from Validate() into ModelState. My actions look like this now:

        [HttpPost, ValidateAntiForgeryToken]
        public ActionResult Edit(CategoryEditModel model)
        {
            ConsumeErrors(model.Validate());
            if (!ModelState.IsValid)
                return View(model);
            Category entity = categoryRepository.SelectById(model.Id);
            entity.SetTitle(model.Title);
            entity.Sequence = model.Sequence;
            entity.Description = model.Description;
            entity.Visible = model.Visible;
            categoryRepository.Update(entity);
            return RedirectToAction("Manager", "Content");
        }

I’m quite happy with this arrangement.