How do I bind such a complex model with multiple layers that contain multiple objects?
Right now I pass the model to the view - (populating a form / a check box tree) and I would like the exact model back (SubjectSelectionModel) but it's not binding correctly.
Could anyone elaborate on the process I need to take in order to bind these correctly in my view?
View Model:
public class SubjectSelectionModel
{
public IList<Subject> Subjects { get; set; }
}
Subject Class:
public class Subject
{
public String Name { get; set; }
public IList<Bin> Bins { get; set; }
public Subject()
{
}
public Subject(IList<Course> courses)
{
}
}
Bin Class:
public class Bin
{
public Subject Subject { get; set; }
public int Amount { get; set; }
public IList<Foo> Foos { get; set; }
}
Foo Class:
public class Foo
{
public int Number { get; set; }
}
This is where Editor Templates come in handy. Rather than messing around with this, you can use simple editor templates to handle all the grunt work for you.
You would create several templates in ~/Views/Shared/EditorTemplates, and then in your primary view it should look like this:
View.cshtml
#model SubjectSelectionModel
#using(Html.BeginForm()) {
#EditorFor(m => m.Subjects)
<input type="submit" />
}
Subject.cshtml
#model Subject
#Html.EditorFor(m => m.Name)
#Html.EditorFor(m => m.Bins)
Bin.cshtml (I assume you don't want to render Subject, this would be an infinite loop)
#model Bin
#Html.EditorFor(m => m.Amount)
#Html.EditorFor(m => m.Foos)
Foo.cshtml
#model Foo
#Html.EditorFor(m => m.Number)
Obviously, you may want to change the html formatting to whatever you want, but that's essentially it.
You need a for loop for the objects so MVC can bind using the index in the collection.
Example:
for (int subjectIndex = 0; subjectIndex < Model.Subjects.Count; subjectIndex++) {
#Html.TextBoxFor(x => x.Subjects[subjectIndex].Name)
for (int binIndex = 0; binIndex < Model.Subjects.Bins.Count; binIndex++) {
#Html.TextBoxFor(x => x.Subjects[subjectIndex].Bins[binIndex].Amount)
}
}
..etc.
I gave a similar response to a similar question, here: Generating an MVC RadioButton list in a loop
Related
A quick question:
So, I am developing a small MVC/C# application and I am using ViewModel to pass data to my view. The ViewModel is actually a combination of 3 Models.
My Models:
public class Sourcing_ProfileSourcingAreas
{
public string Area { get; set; }
public string Details { get; set; }
}
public class DefCountry
{
public string CountryName { get; set; }
public string CountryCode { get; set; }
}
My ViewModel:
public class SourcingIndex
{
public List<DefCountry> CountryLst { get; set; }
public List<Sourcing_ProfileSourcingAreas> AreaLst { get; set; }
}
On my view, I put this line at the top #model SourcingIndex to declare that I will be using this ViewModel
I also was easily able to specify which model I want to display by using foreach loop, for example:
#foreach (Sourcing_ProfileSourcingAreas area in Model.AreaLst)
{
<tr>
<td>#area.Area</td>
<td>#area.Details</td>
</tr>
}
And
#foreach (DefCountry ctry in Model.CountryLst)
{
<option>#ctry.CountryName</option>
}
However, I am not able to create #Html.TextBox and assign it to a specific property in a model!
If possible, I want it to be somthing like this: #Html.TextBoxFor(model => model.AreaLst.Area)
Thank you
It's a bit of a weird quirk about Razor. If you try and access the objects in a foreach loop it struggles to resolve where in the model it is. You need to use this syntax:
#for (int x = 0; x < Model.CountryLst.Count(); x++)
{
#Html.TextBoxFor(t => t.CountryLst[x].CountryName)
}
this should produce an input something like
<input name="countryLst[1].CountryName"/>
#for(var i = 0; i < Model.CountryLst.Count(); i++)
{
<text>#Html.TextBoxFor(p=>Model.CountryLst[i].CountryCode)</text>
}
So I am currently studying and analyzing the use of ViewModels.
In my Application (a so called "Restaurant") I want the ability for my "users" to create a menu.
When they want to create a menu: They can choose the name + the amount of persons that can join the menu. BUT also, they can add an amount of dishes that are already in the restaurant. This will be in the style of checkboxes and an 'Create'-Button at the end.
This means I had to use a ViewModel. I am currently trying to give the possibility to add a list of dishes to a menu for the creation. But I'm stuck at the for loop, used to loop through the dishes. Or better, I'm stuck at the whole concept:
What is the best way to display all the already created dishes to the CreateMenu View? Is it still possible to loop through a ViewBag if I will add them in a ViewBag?
Lets say I successfully tried to do what I wanted to do. How would I create a new Menu based (or extracted?) from the ViewModel?
In my Code, please note that the Menu - Model cannot be changed really because I already use a list of Dishes from it (In another view, where I display all the menu's and their dishes).
also ignore the possibility of wrong names or spelling mistakes in data, since I translated everything from 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 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; }
}
Controller
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);
}
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 i = 0; i < Model.AddedDishes.Count; i++)
{
<tr>
<td>
#Html.DisplayFor( .Name)
#Html.HiddenFor(item => .Id)
#Html.CheckBoxFor(item => .Checked)
</td>
</tr>
}
E D I T E D _ U P D A T E (SEE BELOW)
Okay So I think I'm close now,
I edited my classes as the 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 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>
}
I did this after watching about 10 tutorials about ViewModels, is my next approach better than the first one?
I think so because i get the following on my screen:
I was thinking what the next approach would be. I was thinking about comparing the 2 lists (1 of the viewmodel, 1 passed) and see the checkbox statuses?
UPDATE
After Stephen Muecke's answer I re-edited my code but found a problem that I can't seem to understand.
The answer says I should be in the position of a 1-to-many table in the form as a 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);
However, what we've learned at school was, that if you create lists in the data-models of the entities, linked tables will be automatically generated in the Database. And that is exactly what my DB has done (without the creation of the MenuDish class):
MenuGerechts stands for MenuDish.
This is the automatically created table done by the entity framework.
That brings me to the following questions. I have re-edited the controller to the 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 get the Object reference not set to an instance of an object error and I'm understanding why ofcourse.
I have done the changes since the Data-Model Menu, already has a List of Dishes. But assuming the answer of S. Muecke, this isn't the correct way to solve this ViewModel since he proposes the use of a New Class (that is created to support the one-to-many relationship)?
This brings me to the conclusion of the following questions:
Why is it impossible or not-recommended to directly add the selected dishes to the menu instance?
Is it always needed to create the in between table 'MenuDish' in a Data-model?
Will the following code still work (showing the menu's and their 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 the following:
What would be good motivations to do this?
UPDATE 2
So I have included the following in my project:
** I have used the Table()- annotation to make it use the one that's already 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 then actually created new menus successfully! But when I go to the overview menu page (from the pic above), it only shows the Name of the menu, and not the list of meals that it includes.
The Database however didn't allow my MenuDish link table to be used for my newly created class (it created a new one, and renamed the old one with the 'old' menus with a '1' behind it:
Hence why I was asking my previous questions. Does this mean my whole approach to this exercise was wrong?
New Question:
My menuCreate ViewModel only works if i Select 1 dish? Why is this so? I get the following error The role 'MenuGerechts_Menu_Source' of the relationship 'VBExamen.Models.MenuGerechts_Menu' has multiplicity 1 or 0..1.
Firstly a view model should not contain properties which are data models. It should contains only properties which you display/edit in the view, and I recommend you read What is ViewModel in MVC?.
Based in the image of the form you have shown, your view models needs to be (display and validation attributes omitted for simplicity)
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; }
}
and the controller GET method
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);
}
Then in the view (LabelFor() and ValidationFor() methods omitted for simplicity)
#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" />
}
And finally the POST method will be
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
}
Side note: Remove the [NotMapped] public bool Checked { get; set; } property from your data model.
This is only the answer to your first question... I gotta get back to work :)
I strongly advise you to use Entity Framework for storing this information, as creating the data context, Initializer(Entity Framework Requirements) and View Model will allow you to scaffold everything in your project including controllers and views. This means you take the information from the ViewModel class rather than from the view bag.
Scaffolding means that Visual Studio will create all your code to CRUD(Create, Read, Update, Delete) Controllers and Views to allow this to happen. Freeing you from either 45 mins of furious typing or hours of banging your head against a wall.
So lets do this, First we create our own context class inheriting from DbContext (A part of Entity Framework):
public class MenuContext : DbContext {
public MenuContext() : base("MenuContext") {
}
The base referenced here specifies the name in your web.config file of your connection string which we will set up momentarily. Alternatively you can specify your connection string in place of the name.
public DbSet<MenuViewModel> Menus { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
The Initializer class we will set up next populates the database if the database does not already exist at the connection string location.
class MenuInitializer : CreateDatabaseIfNotExists<MenuContext> {
protected override void Seed(MenuContext context) {
// This sets up your database to populate with whatever you type into this method.
}
}
Now you are able to go to your solution explorer, right click on your controllers folder and click add - Controller. Then specify that you want MVC 5 Controller with views, using Entity Framework. Click - Add.
A dialog will show, specify your view model, the context class we set up, make sure "Generate Views" is selected, name your controller and BAM! Build your project and view your auto created everything!
I'm trying to find the best correct way to do the following:
I have a ViewModel for a character editor called CharacterViewModel. This CharacterViewModel is populated with a Character object, a list of available ability scores a character can have, which are in another table.
I created an edit template for the drop down, and I'm trying to find a way to recuperate the list of edited abilities. I can't seem to get them back on the controller.
Here is the ViewModel code:
public class CharacterViewModel : DbContext
{
public Character Character { get; set; }
[UIHint("CharacterAbilityScores")]
public IEnumerable<CharacterAbilityScore> CharacterAbilityScores { get; set; }
public IEnumerable<SelectListItem> AbilityScoresSelectList { get; set; }
public IEnumerable<AbilityModifiersAndBonusSpellDTO> AbilityModifiersAndBonusSpellDTO { get; set; }
public CharacterViewModel()
: base("name=CharacterModels")
{
}
}
Here is the controller code for populating the ViewModel:
public async Task<ActionResult> Edit(int? id)
{
Character character = db.Characters.Find(id);
var model = new CharacterViewModel();
model.Character = character;
model.CharacterAbilityScores = character.CharacterAbilityScores;
// Creating the list of ability scores for the view
model.AbilityScoresSelectList = from amabs in db.AbilityModifiersAndBonusSpells
select new SelectListItem()
{
Value = amabs.score.ToString(),
Text = amabs.score.ToString()
};
return View(model);
}
The edit method signature in the controller (the CharacterAbilityScores property and the other complex ones are always empty on the return trip):
public async Task<ActionResult> Edit(CharacterViewModel characterViewModel)
Here is the related code in the edit view:
#model CampaignManager.Models.CharacterViewModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.EditorFor(model => model.CharacterAbilityScores, new { AbilityScoresSelectList = Model.AbilityScoresSelectList })
</div>
}
And finally, the EditorTemplate:
#model IEnumerable<CampaignManager.Entities.CharacterAbilityScore>
<table>
#foreach (var abilityScore in Model)
{
<tr>
<td>#abilityScore.Ability.Abbreviation</td>
<td>
#{
if (ViewData["AbilityScoresSelectList"] != null)
{
#Html.HiddenFor(z => abilityScore);
#Html.HiddenFor(z => abilityScore.AbilityId);
#Html.DropDownListFor(x => abilityScore.AbilityId, (IEnumerable<SelectListItem>)ViewData["AbilityScoresSelectList"], dropDownHTMLOptions);
}
}
</td>
<tr>
}
</table>
I've tried many different HiddenFor tricks, storing the whole collection, storing different id's... I'm a bit lost in there I'll admit. Maybe I'm doing this all wrong and I need another approach?
UPDATE
Here is the model for the CharacterAbilityScore entity:
public partial class CharacterAbilityScore
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CharacterId { get; set; }
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AbilityId { get; set; }
public int AbilityScore { get; set; }
public virtual Ability Ability { get; set; }
public virtual AbilityModifiersAndBonusSpell AbilityModifiersAndBonusSpell { get; set; }
public virtual Character Character { get; set; }
}
EditorFor() is designed to wok with collection where the EditorTemplate is the type in the collection (in your case you have made the EditorTemplate's model a collection (not the type) and are then giving each element a duplicate id attribute (invalid html) and duplicate name attributes (which cant be bound to a collection).
Change the template (Views/Shared/EditorTemplates/CharacterAbilityScore.cshtml) to:
#model CampaignManager.Entities.CharacterAbilityScore
<tr>
<td>#Html.DisplatFor(m => m.Ability.Abbreviation)</td>
<td>#Html.DropDownListFor(m => m.AbilityId, (IEnumerable<SelectListItem>)ViewData["AbilityScoresSelectList"])</td>
</tr>
and in the main view
#model CampaignManager.Models.CharacterViewModel
#using (Html.BeginForm())
{
<table>
#Html.EditorFor(model => model.CharacterAbilityScores, new { AbilityScoresSelectList = Model.AbilityScoresSelectList })
</table>
}
Side notes:
You have not posted the model for CharacterAbilityScore so a have
assumed it contains properties Abbreviation (for display only) and
AbilityId (associated with the dropdown).
You can not use #Html.HiddenFor() on a complex object (the value
will be the .ToString() output of the object) and having
#Html.HiddenFor() for the same property as the dropdown (and
located before #Html.DropDownListFor()) means that you will bind
to the hidden input on post back (i.e. the original value, not the
selected value from the dropdown)
I also recommend your view models do not derive from DbContext.
The purpose of a view model is to define the properties you want to
display/edit in the view
I am relying heavily on EditorTemplates in my application, but I've run into a problem which I can not seem to solve, without not moving away from EditorTemplates for drop down lists.
Consider this (View)Model:
public class CreateStudentViewModel
{
public DropDownList StudentTypes { get; set; }
public CreateStudent Command { get; set; }
}
public class DropDownList {
public string SelectedValue { get; set; }
public IList<SelectListItem> Items { get; set; }
}
public class CreateStudent {
public string Name { get; set; }
public int StudentTypeId { get; set; }
}
I use this to provide a way for the frontend user to set the student type, this is done with the following EditorTemplate:
#model DropDownList
<div class="form-group#(Html.ValidationErrorFor(m => m.SelectedValue, " has-error"))">
#Html.LabelFor(m => m)
#Html.DropDownListFor(m => m.SelectedValue, Model.Items)
#Html.ValidationMessageFor(m => m.SelectedValue, null)
</div>
And used within my view:
#Html.EditorFor(m => m.StudentTypes)
Now this EditorTemplate is binding to the StudentTypes.SelectedValue on DropDownList, which is good in some cases - but I need to bind this to my Model.Command.StudentTypeId here.
I know I can move all this code directly to the view and directly bind it, instead of having it inside a EditorTemplate, but I will try my best to avoid this.
Ideally I am thinking of extending the EditorFor to provide a way like:
#Html.EditorFor(m => m.StudentTypes, new { selectedValue = Model.Command.StudentTypeId });
But I can not seem to translate this to something like:
#Html.DropDownList(#ViewBag.selectedValue.ToString(), Model.Items);
As this just places the value (int) as the field name. Any suggestions is welcome! :-)
Your chief problem here is encapsulating your drop down list in a class in order to rely on the C# type editor template convention. Instead, just use your model directly and use UIHint to tell Razor to use a particular template. Here's a simplified version of what I use:
View Model
[UIHint("Choice")]
public int SelectedFoo { get; set; }
public IEnumerable<SelectListItem> FooChoices { get; set; }
Views\Shared\EditorTemplates\Choice.cshtml
#{
var choices = ViewData["choices"] as IEnumerable<SelectListItem> ?? new List<SelectListItem>();
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(ViewData.ModelMetadata.ModelType) && ViewData.ModelMetadata.ModelType != typeof(string))
{
#Html.ListBox("", choices)
}
else
{
#Html.DropDownList("", choices)
}
}
View
#Html.EditorFor(m => m.SelectedFoo, new { choices = Model.FooChoices })
In case it's not obvious, the conditional in the editor template determines if the property is a value or list type, and either uses a drop down list control or listbox control, respectively.
I am having trouble understanding if this is possible or not. I have a stored procedures and a view model classes and one controller. Is it possible that I could add another view model class with a different stored procedure to use one controller and display the the information from both stored procedures into one view?
This is my current Model:
public class People
{
public class Jobs
{
public int jobID { get; set; }
public string jobName { get; set; }
}
public class Names
{
public int userId{get;set}
public string userName {get; set;}
}
}
Yes, this is totally possible. Simply create a view model that contains the properties that would be populated by your stored procedures and then pass that to your view:
public class PeopleViewModel
{
public List<Job> Jobs { get; set; }
public List<Name> Names { get; set; }
}
Then the action method:
public ActionResult Index()
{
var model = new PeopleViewModel();
model.Jobs = // ... fetch from database
model.Names = // ... fetch from database
return View(model);
}
Update per comments
As you've stated Jobs and Names are collections, the model code you've shown won't solve your problem. It sounds like this is what you really want:
public class Job // Notice I've removed the pluralisation
{
public int jobID { get; set; }
public string jobName { get; set; }
}
public class Name // Notice I've removed the pluralisation
{
public int userId { get; set; }
public string userName { get; set; }
}
Now, your People class can contain lists of those:
public class People
{
public List<Job> Jobs { get; set; }
public List<Name> Names { get; set; }
}
Now you can wrap this in a view model:
public class PeopleViewModel
{
public People People { get; set; }
}
Then you would populate it from your action:
public ActionResult Index()
{
var model = new PeopleViewModel();
model.People = // ... fetch from database
return View(model);
}
The thing is, if Jobs and Names are unrelated, there's no point in wrapping them in a People class first. You'd simply do the following (as discussed above):
public class PeopleViewModel
{
public List<Job> Jobs { get; set; }
public List<Name> Names { get; set; }
}
Think of a view model as a way of representing just a slice of the data you want to display in your view, nothing more, nothing less. All the code above does is define what data you'd like to see in that view. As the properties on the view model are public, you can populate them in your controller's action using whatever method you currently use.
Second Update
I think it would probably be helpful to show you a full example as to how fits together for your views. Firstly, I'll show you the quick and dirty way of accessing the data in your view model and then afterwards I'll show you how to do the same with DisplayTemplates.
Quick and Dirty Approach
So first up, let's assume we have an action called Index like the following:
public ActionResult Index()
{
var model = new PeopleViewModel();
model.Jobs = // ... fetch from database
model.Names = // ... fetch from database
return View(model);
}
Here's the Index view:
#model PeopleViewModel
#foreach (var job in Model.Jobs)
{
#Html.DisplayFor(m => job.JobId)
#Html.DisplayFor(m => job.JobName)
}
#foreach (var name in Model.Names)
{
#Html.DisplayFor(m => name.UserId)
#Html.DisplayFor(m => name.UserName)
}
Notice how the view expects an instance of PeopleViewModel and it's simply looping over each individual item in each collection, printing the contents. However, whilst this is fine for very simple views, mixing logic with your HTML becomes a maintenance headache with more complicated views and larger projects. With that in mind, we can make use of DisplayTemplates instead.
Second Approach - Using Templates
The action in your controller remains the same. However, we need to make some other changes. What we're working towards is turning the Index view into this:
#model PeopleViewModel
#Html.DisplayFor(m => m.Jobs)
#Html.DisplayFor(m => m.Names)
By calling Html.DisplayFor in this way, it will try to locate a DisplayTemplate which corresponds to the data type it's being passed. In this case, the helper is smart enough to know that we're passing collections to it and so it will look for a template that matches the data type the collection contains. That means we need to make templates for the types Job and Name. To do that, follow these steps:
Create a DisplayTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\DisplayTemplates).
Create a strongly-typed view in that directory with the name that matches your model (i.e. in this case you would make two views, which would be called Job.cshtml and Name.cshtml, respectively).
Now you can move all of the display logic into those two templates. So they would look like the following:
Job.cshtml
#model Job
#Html.DisplayFor(m => m.JobId)
#Html.DisplayFor(m => m.JobName)
Name.cshtml
#model Name
#Html.DisplayFor(m => m.UserId)
#Html.DisplayFor(m => m.UserName)
Hopefully that clears things up a little.