MVC 5:用于创建的 ViewModel / 传递列表

MVC 5: ViewModel / Passing lists for the create

所以目前正在研究和分析ViewModels的使用

在我的应用程序(所谓的 "Restaurant")中,我希望我的 "users" 能够创建菜单。

当他们想创建菜单时:他们可以选择名称+可以加入菜单的人数。但是,他们也可以添加餐厅已有的菜肴。这将采用复选框的样式,最后是 'Create'-Button。

这意味着我必须使用 ViewModel。我目前正在尝试提供将菜肴列表添加到菜单中以进行创建的可能性。但我被困在 for 循环中,用于遍历盘子。或者更好的是,我坚持整个概念:

在我的代码中,请注意菜单 - 模型实际上无法更改,因为我已经使用了其中的菜肴列表(在另一个视图中,我显示了所有菜单及其菜肴)。

也忽略数据中名称错误或拼写错误的可能性,因为我从佛兰德语翻译了所有内容

型号

public class Menu
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Range(0,10)]
    public int AmountPersons { get; set; }
    [Range(0,double.MaxValue)]
    public double Price { get; set; }
    public virtual List<Dish> Dishes { get; set; }
}

public class Dish
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public enum Types { VOORGERECHT, HOOFDGERECHT, DRANK, DESSERT}
    public Types Type { get; set; }
    public double Price { get; set; }
    public virtual List<Menu> Menus { get; set; }
    public virtual List<Table> Tables { get; set; }
    //Checked used for the 'checkbox' in the CreateMenu-View
    [NotMapped]
    public bool Checked { get; set; }
}

public class MenuViewModel
{
    public Menu Menu { get; set; }
    public List<Dish> AddedDishes { get; set; }
}

控制器

public ActionResult CreateMenu( )
{
    MenuViewModel gm = new MenuViewModel();
    // Assign ALL already created dishes to the list that the user can choose.
    // AddedDishes is wrong? ViewBag preferred?
    gm.AddedDishes = db.Dishes.ToList();
    return View(gm);
}

// Add the Menu to all the Menu's in the Database.
[HttpPost]
public ActionResult MenuAanmaken(MenuModel gm)
{
    // code to save the menu with all the added dishes to the database
    // Conflict!? Cannot convert the MenuViewModel to the Menu-model How do we need to extract the Menu and the AddedDishes list 
    // to a menu and save that one to the database?
    db.Menus.Add(gm);
    return View(gm);
}

查看

@using VBExamen.Models
@model MenuViewModel
....

@Html.LabelFor(m => m.Menu.Name)
@Html.EditorFor(m => m.Menu.Name)
@Html.LabelFor(m => m.Menu.AmountPersons)
@Html.EditorFor(m => m.Menu.AmountPersons)

@for(int i = 0; i < Model.AddedDishes.Count; i++)
{
    <tr>
        <td>
            @Html.DisplayFor( .Name)
            @Html.HiddenFor(item => .Id)
            @Html.CheckBoxFor(item => .Checked)
        </td>
    </tr>
}

编辑_更新(见下文) 好的,我想我现在很接近了,

我编辑了我的 classes 如下:

public class MenuViewModel<T>
{
    public Menu Menu { get; set; }
    public List<T> DishList { get; set; }
    public MenuViewModel()
    {
        this.Lijst = new List<T>();
    }
}

控制器

public ActionResult CreateMenu(MenuViewModel<Dish> model )
{
    model.DishList = db.Gerechten.ToList();
    return View(model);
}

[HttpPost]
public ActionResult CreateMenu(MenuViewModel<Dish> model,List<Gerecht> SelectedList)
{
    Menu t = new Menu();
    t.Naam = gm.Menu.Naam;
    t.AmountPersons = gm.Menu.AmountPersons;
    t.Dishes = SelectedList;
    db.Menus.Add(t);
    return View("Menus", model);
}

查看函数创建列表

@for (int i = 0; i < Model.DishList.Count(); i++)
{
    <tr>
        <td>
            @Html.Label(Model.DishList[i].Naam)
            <input type="hidden" name=@String.Format("DishList[{0}].Id", i) value=@Model.DishList.ElementAt(i).Id />
            <input type="hidden" name=@String.Format("DishList[{0}].Name", i) value=@Model.DishList.ElementAt(i).Name />
            <input type="checkbox" name=@String.Format("DishList[{0}].value", i) />
            <input type="hidden" name=@String.Format("DishList[{0}].value", i) value="false" />
        </td>
        <br />
    </tr>
}

我是在看了大约 10 篇有关 ViewModel 的教程后才这样做的,我的下一个方法是否比第一个更好?

我想是的,因为我在屏幕上看到以下内容:

我在想下一个方法是什么。我正在考虑比较 2 个列表(1 个视图模型,1 个通过)并查看复选框状态?

更新

在 Stephen Muecke 的回答后,我重新编辑了我的代码,但发现了一个我似乎无法理解的问题。

答案说我应该处于一对多 table 的位置,形式为 class:

// You have not indicated the 1-many table the dishes are saved to so adjust as required
        MenuDish dish = new MenuDish()
        {
            MenuId = menu.ID,
            DishId = dish
        };
        db.MenuDishes.Add(dish);

但是,我们在学校学到的是,如果您在实体的数据模型中创建列表,linked tables 将自动在数据库中生成。这正是我的数据库所做的(没有创建 MenuDish class):

MenuGerechts 代表 MenuDish。 这是由 entity framework 自动创建的 table。 这让我想到了以下问题。我已将控制器重新编辑为以下内容:

 [HttpPost]
        public ActionResult MenuAanmaken(MenuVM model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            IEnumerable<int> selectedDishes = model.Dishes.Where(x => x.IsSelected).Select(x => x.ID);
            Menu menu = new Menu()
            {
                Naam = model.Name,
                AantalPersonen = model.AmountPersons
            };

             foreach (int id in selectedDishes)
                {
                    Dish g = db.Dishes.Find(id);
                    menu.Dishes.Add(g);

                };

            db.Menus.Add(menu);
            db.SaveChanges(); 

            return RedirectToAction("Menus", "Menu");
        }

我收到 Object reference not set to an instance of an object 错误,我当然明白为什么。

我已经完成了数据模型菜单的更改,已经有一个菜肴列表。但是假设 S. Muecke 的回答,这不是解决这个 ViewModel 的正确方法,因为他建议使用 New Class(创建它是为了支持一对多关系)?

这使我得出以下问题的结论:

控制器:

   public ActionResult Menus()
            {
                List<Menu> menus = db.Menus.ToList();
                return View(menus);
            }

查看:

@model IEnumerable<VBExamen.Models.Menu>

@{
    ViewBag.Title = "Menus";
}

<h2>Menus</h2>

<p>
    @Html.ActionLink("Create New Menu", "CreateMenu")
</p>

@foreach (var item in Model)
{
    <table>


        <ul>
            <p>@Html.DisplayFor(modelItem => item.Name)</p>

            @foreach (var g in item.Dishes)
            {
                <li>
                    @Html.DisplayFor(modelItem => g.Name)
                </li>
            }
        </ul>
    </table>
}

输出如下:

这样做的良好动机是什么?

更新 2

所以我在我的项目中包含了以下内容: ** 我使用了 Table()- 注释来让它使用已经创建的注释**

我真的成功创建了新菜单!但是当我进入概览菜单页面(从上图)时,它只显示菜单的名称,而不是它包含的餐点列表。

然而,数据库不允许我的 MenuDish link table 用于我新创建的 class(它创建了一个新的,并用'old' 后面带有“1”的菜单:

这就是我问之前问题的原因。这是否意味着我对这个练习的整个方法都是错误的?

新问题: 我的 menuCreate ViewModel 仅在我 Select 1 道菜时有效?为什么会这样?我收到以下错误 The role 'MenuGerechts_Menu_Source' of the relationship 'VBExamen.Models.MenuGerechts_Menu' has multiplicity 1 or 0..1.

这只是您第一个问题的答案...我得回去工作了:)

我强烈建议您使用 Entity Framework 来存储此信息,因为创建数据上下文、初始化程序(Entity Framework 要求)和视图模型将允许您搭建项目中的所有内容,包括控制器和意见。这意味着您从 ViewModel class 而不是视图包中获取信息。

脚手架意味着 Visual Studio 会将您的所有代码创建到 CRUD(创建、读取、更新、删除)控制器和视图,以实现这一点。让您从 45 分钟的拼命打字或数小时的头撞墙中解脱出来。

所以让我们这样做,首先我们创建我们自己的上下文 class 继承自 DbContext(Entity Framework 的一部分):

    public class MenuContext : DbContext {

    public MenuContext() : base("MenuContext") { 

}

此处引用的基础指定了您的 web.config 文件中我们将立即设置的连接字符串的名称。或者,您可以指定连接字符串来代替名称。

    public DbSet<MenuViewModel> Menus { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

    }

}

如果数据库在连接字符串位置尚不存在,我们接下来将设置的初始化程序 class 将填充数据库。

    class MenuInitializer : CreateDatabaseIfNotExists<MenuContext> {

    protected override void Seed(MenuContext context) {

        // This sets up your database to populate with whatever you type into this method.
    }
}

现在您可以转到您的解决方案资源管理器,右键单击您的控制器文件夹并单击添加 - 控制器。然后使用 Entity Framework 指定您想要带视图的 MVC 5 控制器。单击 - 添加。

将显示一个对话框,指定您的视图模型,我们设置的上下文 class,确保选择 "Generate Views",命名您的控制器和 BAM!构建您的项目并查看您自动创建的所有内容!

首先,视图模型不应包含属于数据模型的属性。它应该只包含您在视图中 display/edit 的属性,我建议您阅读 What is ViewModel in MVC?.

根据您显示的表单图像,您的视图模型需要(为简单起见省略了显示和验证属性)

public class MenuVM
{
    public int? ID { get; set; } // included so this can be used for editing as well as creating
    public string Name { get; set; }
    public int AmountPersons { get; set; }
    public List<DishVM> Dishes { get; set; }
}
public class DishVM
{
    public int ID { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

和控制器 GET 方法

public ActionResult CreateMenu( )
{
    // Get all the dishes from the database
    var dishes = db.Dishes; // modify to suit
    // Initialize the view model
    var model = new MenuVM()
    {
        Dishes = dishes.Select(x => new DishVM()
        {
            ID = x.Id,
            Name = x.Name
        }).ToList()
    };
    return View(model);
}

然后在视图中(为简单起见省略了LabelFor()ValidationFor()方法)

@model MenuVM
@using (Html.BeginForm())
{
    @Html.TextBoxFor(m => m.Name)
    @Html.TextBoxFor(m => m.AmountPersons )
    for(int i = 0; i < Model.Dishes.Count; i++)
    {
        <div>
            @Html.HiddenFor(m => m.Dishes[i].ID)
            @Html.HiddenFor(m => m.Dishes[i].Name)
            @Html.CheckBoxFor(m => m.Dishes[i].IsSelected)
            @Html.LabelFor(m => m.Dishes[i].IsSelected, Model.Dishes[i].Name)
        </div>
    }
    <input type="submit" value="Create" />
}

最后 POST 方法将是

public ActionResult CreateMenu(MenuVM model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Initialize and save the Menu
    Menu menu = new Menu()
    {
        Name = model.Name,
        AmountPersons = model.AmountPersons
    };
    db.Menus.Add(menu);
    db.SaveChanges(); // you now have the ID of the new menu
    // Save the dishes associated with the menu
    IEnumerable<int> selectedDishes = model.Dishes.Where(x => x.IsSelected).Select(x => x.ID);
    foreach(int id in selectedDishes)
    {
        // You have not indicated the 1-many table the dishes are saved to so adjust as required
        MenuDish dish = new MenuDish()
        {
            MenuId = menu.ID,
            DishId = dish
        };
        db.MenuDishes.Add(dish);
    }
    db.SaveChanges(); // save the selected dishes
    return RedirectToAction(...); // redirect somewhere
}

旁注:从数据模型中删除 [NotMapped] public bool Checked { get; set; } 属性。