Joseph
Joseph

Reputation: 2726

Scaffolding ModelView creates underlying database tables

I'm trying to use ViewModels for the first time using AutoMapper. I have two models:

public class Item
{
    public int ItemId { get; set; }
    public bool Active { get; set; }
    public string ItemCode { get; set; }
    public string Name { get; set; }
    public List<ItemOption> ItemOptions { get; set; }
    //...
}

public class ItemOption
{
    public int ItemOptionId { get; set; }
    public string Name { get; set; }
    public string Barcode { get; set; }
    //...
}

Which I have turned into two ViewModels:

public class ItemDetailViewModel
{
    public int ItemDetailViewModelId { get; set; }
    public int ItemId { get; set; }
    public bool Active { get; set; }
    public string ItemCode { get; set; }
    public string Name { get; set; }
    public List<ItemDetailItemOptionViewModel> ItemOptions { get; set; }
}

public class ItemDetailItemOptionViewModel
{
    public int ItemDetailItemOptionViewModelId { get; set; }
    public int ItemOptionId { get; set; }
    public string Name { get; set; }
    public string Barcode { get; set; }

}

I then set the following in my application start-up:

Mapper.CreateMap<Item, ItemDetailViewModel>();
Mapper.CreateMap<ItemOption, ItemDetailItemOptionViewModel>();

Finally I scaffolded my ItemDetailViewModel:

enter image description here

I then built my project and added a new Item through /Item/Create

I had a look in the database expecting to see that I would have an entry in the Item table, but instead I have ItemDetailViewModel and ItemDetailItemOptionViewModel tables, which I wasn't expecting and the data is is ItemDetailViewModel.

I assume I have done something wrong with my scaffolding? How do I scaffold off the ViewModel without making it part of the main business models?

Further Details

If it isn't possible to scaffold the controller with a ViewModel, then how do I reference the ViewModel in the controller and save changes back to the database?

For example what would the following change to once I remove ItemDetailViewModel from the db context?

    //
    // POST: /Item/Create

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(ItemDetailViewModel itemdetailviewmodel)
    {
        if (ModelState.IsValid)
        {
            db.ItemDetailViewModels.Add(itemdetailviewmodel);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(itemdetailviewmodel);
    }

Further Details [2]

So am I correct that my Index/Details should work as so or is there a better way of doing it?

    //
    // GET: /Item/

    public ActionResult Index()
    {
        var items = db.Items.ToList();
        var itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(items);
        return View(itemdetailviewmodel);
    }

    //
    // GET: /Item/Details/5

    public ActionResult Details(int id = 0)
    {
        ItemDetailViewModel itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(db.Items.Find(id));
        if (itemdetailviewmodel == null)
        {
            return HttpNotFound();
        }
        return View(itemdetailviewmodel);
    }

Upvotes: 1

Views: 234

Answers (1)

trailmax
trailmax

Reputation: 35116

Scaffolding is not that intelligent. The standard controller scaffolding template is creating a DbContext with the controller model and presumes you are working with the DB models, not view models and it does not use Automapper. So you'll need to either not use scaffolding, or check what it has done before using it.

And nothing is wrong with the way you use scaffolding, it is just not supposed to do what you expect.

Update this is how you do this without scaffolding

// Without Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
    if (ModelState.IsValid)
    {
        var item = new Item() 
        {
            Active = model.Active,
            ItemCode = model.ItemCode,
            Name = model.Name,
            ItemOptions = // code to convert from List<ItemDetailItemOptionViewModel> to List<ItemOption>
        }
        db.Items.Add(item);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(model);
}


// with Automapper - not recommended by author of Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
    if (ModelState.IsValid)
    {
        var item = Automapper.Mapper.Map<Item>(model);

        db.Items.Add(item);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(model);
}    

You'll need to modify your DbContext to have IDbSet<Item> Items instead of IDbSet<ItemDetailViewModels> ItemDetailViewModels.

Automapper was created to map from Domain Models to View Models and not the other way. I have done that for a while, but this is troublesome and causes subtle bugs and other maintenance problems. Even Jimmy Bogard himself says you should not map from view models to domain models.

Upvotes: 1

Related Questions