c# - MVC 5: ViewModel / Passing lists for the create -
so studying , analyzing use of viewmodels.
in application (a called "restaurant") want ability "users" create menu.
when want create menu: can choose name + amount of persons can join menu. also, can add amount of dishes in restaurant. in style of checkboxes , 'create'-button @ end.
this means had use viewmodel. trying give possibility add list of dishes menu creation. i'm stuck @ loop, used loop through dishes. or better, i'm stuck @ whole concept:
what best way display created dishes createmenu view? still possible loop through viewbag if add them in viewbag?
lets tried wanted do. how create new menu based (or extracted?) viewmodel?
in code, please note menu - model cannot changed because use list of dishes (in view, display menu's , dishes).
also ignore possibility of wrong names or spelling mistakes in data, since translated flemish
models
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 'checkbox' in createmenu-view [notmapped] public bool checked { get; set; } } public class menuviewmodel { public menu menu { get; set; } public list<dish> addeddishes { get; set; } }
controller
public actionresult createmenu( ) { menuviewmodel gm = new menuviewmodel(); // assign created dishes list user can choose. // addeddishes wrong? viewbag preferred? gm.addeddishes = db.dishes.tolist(); return view(gm); } // add menu menu's in database. [httppost] public actionresult menuaanmaken(menumodel gm) { // code save menu added dishes database // conflict!? cannot convert menuviewmodel menu-model how need extract menu , addeddishes list // menu , save 1 database? db.menus.add(gm); return view(gm); }
view
@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 = 0; < model.addeddishes.count; i++) { <tr> <td> @html.displayfor( .name) @html.hiddenfor(item => .id) @html.checkboxfor(item => .checked) </td> </tr> }
e d t e d _ u p d t e (see below) okay think i'm close now,
i edited classes following:
public class menuviewmodel<t> { public menu menu { get; set; } public list<t> dishlist { get; set; } public menuviewmodel() { this.lijst = new list<t>(); } }
controller
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); }
view function creating list
@for (int = 0; < 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> }
i did after watching 10 tutorials viewmodels, next approach better first one?
i think because following on screen:
i thinking next approach be. thinking comparing 2 lists (1 of viewmodel, 1 passed) , see checkbox statuses?
update
after stephen muecke's answer re-edited code found problem can't seem understand.
the answer says should in position of 1-to-many table in form class:
// have not indicated 1-many table dishes saved adjust required menudish dish = new menudish() { menuid = menu.id, dishid = dish }; db.menudishes.add(dish);
however, we've learned @ school was, if create lists in data-models of entities, linked tables automatically generated in database. , db has done (without creation of menudish class):
menugerechts stands menudish. automatically created table done entity framework. brings me following questions. have re-edited controller following:
[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"); }
i object reference not set instance of object
error , i'm understanding why ofcourse.
i have done changes since data-model menu, has list of dishes. assuming answer of s. muecke, isn't correct way solve viewmodel since proposes use of new class (that created support one-to-many relationship)?
this brings me conclusion of following questions:
why impossible or not-recommended directly add selected dishes menu instance?
is needed create in between table 'menudish' in data-model?
will following code still work (showing menu's , dishes) after creating new menu's?:
controller:
public actionresult menus() { list<menu> menus = db.menus.tolist(); return view(menus); }
view:
@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> }
which outputs following:
what motivations this?
update 2
so have included following in project: ** have used table()
- annotation make use 1 that's created**
**model: **
[table("menugerechts")] public class menugerechts { [key] [foreignkey("menu")] public virtual int? menuid { get; set; } public virtual menu menu { get; set; }
[foreignkey("dish")] public virtual int? dishid { get; set; } public virtual dish dish { get; set; } }
i have created new menus successfully! when go overview menu page (from pic above), shows name of menu, , not list of meals includes.
the database didn't allow menudish link table used newly created class (it created new one, , renamed old 1 'old' menus '1' behind it:
hence why asking previous questions. mean whole approach exercise wrong?
new question: menucreate viewmodel works if select 1 dish? why so? following error the role 'menugerechts_menu_source' of relationship 'vbexamen.models.menugerechts_menu' has multiplicity 1 or 0..1.
firstly view model should not contain properties data models. should contains properties display/edit in view, , recommend read what viewmodel in mvc?.
based in image of form have shown, view models needs (display , validation attributes omitted simplicity)
public class menuvm { public int? id { get; set; } // included can used editing 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; } }
and controller method
public actionresult createmenu( ) { // dishes database var dishes = db.dishes; // modify suit // initialize view model var model = new menuvm() { dishes = dishes.select(x => new dishvm() { id = x.id, name = x.name }).tolist() }; return view(model); }
then in view (labelfor()
, validationfor()
methods omitted simplicity)
@model menuvm @using (html.beginform()) { @html.textboxfor(m => m.name) @html.textboxfor(m => m.amountpersons ) for(int = 0; < 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" /> }
and post method be
public actionresult createmenu(menuvm model) { if (!modelstate.isvalid) { return view(model); } // initialize , save menu menu menu = new menu() { name = model.name, amountpersons = model.amountpersons }; db.menus.add(menu); db.savechanges(); // have id of new menu // save dishes associated menu ienumerable<int> selecteddishes = model.dishes.where(x => x.isselected).select(x => x.id); foreach(int id in selecteddishes) { // have not indicated 1-many table dishes saved adjust required menudish dish = new menudish() { menuid = menu.id, dishid = dish }; db.menudishes.add(dish); } db.savechanges(); // save selected dishes return redirecttoaction(...); // redirect somewhere }
side note: remove [notmapped] public bool checked { get; set; }
property data model.