I am trying to have a details view in an existing controller that joins two controllers and passes into the view, but everything i have tried so far has not worked. I have 4 data models and what i would like is to use 2 of them Company and Staff. So when i select details on a specific Company it will return all Staff associated to that Company in the same view.
HRDataModel class
public partial class HRDataModel : DbContext
{
public HRDataModel()
: base("name=HRDataModel")
{
}
public virtual DbSet<Company> Companies{ get; set; }
public virtual DbSet<Attribs> Attribs{ get; set; }
public virtual DbSet<Staff> Staffs { get; set; }
....
Company Data Model
[Table("Company")]
public partial class Company
{
public Company()
{
Staffs = new HashSet<Staff>();
}
public virtual ICollection<Staff> Staffs { get; set; }
public int companyId{ get; set; }
[Required]
[StringLength(10)]
public string companyName{ get; set; }
.....
Staff Data Model
public partial class Staff
{
public Staff()
{
Skills = new HashSet<Skill>();
}
public virtual Company Company{ get; set; }
public virtual ICollection<Skill> Skills { get; set; }
public int staffId { get; set; }
.........
And i am trying to get my Details method in CompanyController to show details of all active Companies in the db and also all Staff attached to that Company
[Route("Company/Staff/1}")]
public ActionResult Details(int id)
{
Company co = db.Companies.Find(id);
...How to implement????
return View(bu);
}
If anyone can point me in the right direction that would be great. I have tried and tried but cannot get anything to work?
Since Company includes Staff you can use the include method to include related entities.
var company = db.Companies.Include(c => c.Staffs).FirstOrDefault(x => x.id == companyId);
return View(company);
And in your view:
#foreach(var staff in Model.Staffs) { ... }
You need to pass a data structure which has the company info and staff details to your view. You may pass your existing Comapny entity class to do this. But the problem is, It makes your razor view tightly coupled to your Entity which was generated by the ORM. What if you switch your Data access layer to something else tomorrow. So this solution is not great.
So you should use a view model ( A simple POCO class) which has properties which you need to render in the view. Then read your entity from db in your action method, map it to a vie wmodel instance and send it.
Create a view model like this.
public class CompanyInfo
{
public int Id {set;get;}
public string Name {set;get;}
public List<StaffItem> Staffs {set;get;}
public CompanyInfo()
{
this.Staffs = new List<StaffItem>();
}
}
public class StaffItem
{
public int Id {set;get;}
public string Name {set;get;}
}
In your action method read the Company entity and map it to the view model
public ActionResult Details(int id)
{
var vm = new ComapnyInfo();
var company = db.Companies
.Include(r => c.Staffs)
.FirstOrDefault(x => x.companyId==id);
if(co!=null)
{
//Map the property values now
vm.Name = co.companyName;
vm.Id = co.companyId;
if(co.Staffs!=null)
{
vm.Staffs = co.Staffs.Select(f=> new StaffItem {
Id=f.staffId,
Name = f.staffName}).ToList();
}
}
return View(vm);
}
Now your view should be bound to the CompanyInfo view model
#model YourNamespace.CompanyInfo
<h2>#Model.Name</h2>
<h3>Staffs</h3>
#foreach(var staff in ModelStaffs)
{
<p>#staff.Name</p>
}
If you do not like the manual mapping, You may consider using a mapping libarary like Automapper.
Hi #stackface you dont pass two controllers to get both views for that what you do is create one View Model which is essentially a container for multiple models and pass that into the view from the controller.
E.g. Model 1, Model2, ModelN all are needed so you have a class and in that class it has properties consisting of Model1, Model2 and Model3 so that way you pass in your class which has all the needed models.
E.g.
public class Company{...}
public class Staff{...}
public class ViewModel{
public Company Company {get;set;}
public List<Staff> Staff{get;set;}
}
controller:
{
var viewModel = new ViewModel();
viewModel.Company = db.Companies.FirstOrDefault(x => x.id == companyId);
viewModel.Staff = db.Staff.Where(x => x.CompanyId == campanyId).ToList() //or whatever your properties are called.
Return View(viewModel);
}
Update your view to take type ViewModel.
You can also compose a view by calling controller actions that return partial views.
The advantage is that you can reuse the partial views and their actions, e.g. to show the company details with the same layout on different pages. This increases consistency and maintainability.
The drawback is that you loose flexibility: if a certain page requires a different layout, you should create a new view. Performance might also be lower because you hit the backend with many small operations instead of a single big one.
The combined viewmodel to show both company and staff details only needs to know how to access the required data:
public class CompanyAndStaffDetailsViewModel {
public long CompanyId { get; set; }
public long StaffId { get; set; }
}
The following action renders the combined view:
public ActionResult Details(long companyId, long staffId) {
var viewModel = new CompanyAndStaffDetailsViewModel {
CompanyId = companyId,
StaffId = staffId
};
return View("Details", viewModel);
}
The "Details" View composes the usecase by calling actions to render partial views:
#model CompanyAndStaffDetailsViewModel
#Html.Action("CompanyInfoPartial", "Company", new { companyId = Model.CompanyId })
#Html.Action("StaffInfoPartial", "Staff", new { staffId = Model.StaffId })
The "Company" controller provides a reusable action to render company details:
public ActionResult CompanyInfoPartial(long companyId) {
Company co = db.Companies.Find(companyId);
var model = Mapper.Map<CompanyViewModel>(co); // map persistable entity to ViewModel
return PartialView("_CompanyInfoPartial", model);
}
Then the parital View _CompanyInfoParital.cshtml only has to deal with the Company Info stored in the CompanyViewModel and knows nothing about Staff:
#model CompanyViewModel
#Html.DisplayFor(m => m.CompanyName)
// etc ...
The idea for the StaffInfoPartial action is the same as for CompanyInfoPartial.
Related
I am building my first MVC website and I am struggling to pass a value from one model to another in a ViewModel. The two classes are Music and MusicGenre. In Music, there is a field called 'GenreId' which is an int and is related to the 'Id' field in MusicGenres. I want to return the genre name relating to the Id that's passed over.
Here's what I've got so far:
MusicViewModel vmMusic = new MusicViewModel
{
Music = _context.Music.SingleOrDefault(c => c.Id == id),
MusicGenres = _context.MusicGenres.Where(gi => gi.Id == xxx).ToList()
};
return View(vmMusic);
This all renders nicely minus displaying the right genre. (I replaced xxx with Id but that just uses the id field from Music, not the GenreId field)
So xxx is where I want to pass 'GenreId' but I don't know how. Any help?
Thanks
To expand on my initial comment to the question, I'd just do something like:
var music = _context.Music.SingleOrDefault(c => c.Id == id);
var musicGenres = _context.MusicGenres.Where(gi => gi.Id == music.GenreId).ToList(); // whatever the genre id property is called.
MusicViewModel vmMusic = new MusicViewModel
{
Music = music,
MusicGenres = musicGenre
};
return View(vmMusic);
You should separate your view models from your persistence models. View model is designed to display what you want to display to the users. Persistence model might reflect your data storage structures.
Also from your example it looks like you might already have been using ORM like EntityFramework. EntityFramework provides features like navigation properties and lazy loading.
Either way, I strongly disagree accepted answer's approach, which sends the music and its genre persistence models to the view, when you only need to get the genre name.
Rather, I will approach it this way:
Persistence Models
You might have one-to-may relationship between a genre and an album in your persistence storage:
public class Album
{
public int AlbumId { get; set; }
public string Title { get; set; }
public double Price { get; set; }
public int GenreId { get; set; }
public Genre Genre { get; set; }
}
public class Genre
{
public int GenreId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Album> Albums { get; set; }
}
But obviously you don't want/need to reveal everything you have in your persistence storage to the user. Hence you create view models and only contain what you want to display.
View Models
Let's say you have a view to display information about the selected album. You might create a view model like this:
public class AlbumViewModel
{
public int AlbumId { get; set; }
public string AlbumName { get; set; }
public string Genre { get; set; }
}
Then in the album controller, you can build the view model by using any ORM, lazy loading, etc:
Controller
If you happen to use EntityFramework and have lazy loading enable, you can fetch genre name via its navigation property:
public ActionResult Details(int id)
{
var album = _dbContext.Albums.SingleOrDefault(x => x.AlbumId == id);
if (album == null)
{
return HttpNotFound();
}
var vm = new AlbumViewModel
{
AlbumId = album.AlbumId,
AlbumName = album.Title,
// You can get the genre name via Navigation property, and/or lazy
// loading
Genre = album.Genre.Name
};
return View(vm);
}
Now in a more advanced architecture, the read and write is separated, which is
referred as CQRS. For all the reads (i.e., displaying information to the user), you can build your view model with data directly from executing plain SQL statement.
CQRS with Dapper
using Dapper;
using System.Data;
...
public class AlbumController : Controller
{
private readonly IDbConnection _dbConnection;
public AlbumController(IDbConnection dbConnection)
{
_dbConnection = dbConnection;
}
public ActionResult Details(int id)
{
const string sql = #"
SELECT a.AlbumId, a.Title AS [AlbumName], g.Name AS [Genre]
FROM [Album] a
JOIN [Genre] g ON a.GenreId = g.GenreId
WHERE a.AlbumId = #albumId;
";
var vm = _dbConnection.QuerySingleOrDefault<AlbumViewModel>(sql, new {
albumId = id
});
if (vm == null)
{
return HttpNotFound();
}
return View(vm);
}
}
I'am trying to view information from two tables in a view with the view model, but it does not work.
This gives two tables with information.
public class HeatImage
{
[Key]
public int ImageId { get; set; }
public string name { get; set; }
}
public class HeatingArea
{
[Key]
public int ID { get; set; }
public string Area { get; set; }
}
My viewmodel
public class HeatingAreaViewModel
{
public IEnumerable<HeatImage> heatingImage { get; set; }
public IEnumerable<HeatingArea> heatingArea { get; set; }
}
My controller
public ActionResult Index()
{
HeatingAreaViewModel mymodel = new HeatingAreaViewModel();
mymodel.heatingArea = db.HeatingAreas.ToList();
mymodel.heatingImage = db.HeatImages.ToList();
return View(mymodel);
}
And my view
#model IEnumerable<Project.Models.HeatingAreaViewModel>
#foreach (var item in Model)
{
#item.heatingArea
}
Error
The model item passed into the dictionary is of type
'Project.Models.HeatingAreaViewModel', but this dictionary requires a
model item of type
'System.Collections.Generic.IEnumerable`1[Project.Models.HeatingAreaViewModel]'.
The error message tells it: the View expects IEnumerable of HeatingAreaViewModel, while your controller action sends to the view only HeatingAreaViewModel.
So either correct the controller to send a list of HeatingAreaViewModel, or correct the view to expect only a single instance of HeatingAreaViewModel.
You are sending through appels and expecting oranges, that is why you getting the error.
In your controller you are sending through a single (one) instance of HeatingAreaViewModel. In your view you are making provision to except a list of HeatingAreaViewModel instances (more than 1).
Reading through your replies you want to use both HeatImage and HeatingArea in each loop iteration. You will need to change your view model to accommodate this. For example, create a view model that can accommodate both:
public class HeatViewModel
{
public HeatImage HeatImage { get; set; }
public HeatingArea HeatingArea { get; set; }
}
You will pass this HeatViewModel as a list to your view.
public ActionResult Index()
{
// This is just dummy code
HeatingViewModel model1 = new HeatingAreaViewModel();
// Populate the properties
model1.HeatImage = new HeatImage();
// Populate the properties
model1.HeatingArea = new HeatingArea();
HeatingViewModel model2 = new HeatingAreaViewModel();
// Populate the properties
model2.HeatImage = new HeatImage();
// Populate the properties
model2.HeatingArea = new HeatingArea();
// Now pass this list to the view
List<HeatingViewModel> models = new List<HeatingViewModel>();
return View(models);
}
In your view your code would look something like this:
#model List<Project.Models.HeatingViewModel>
#foreach (var item in Model)
{
<p>item.HeatImage.Name</p>
<p>item.HeatingArea.ID (I have used ID because I don't know what your Area looks like)</p>
}
This way you have both objects in a single loop. You will just have to go and figure out how you are going to populate them, this is where the bulk of the work will be done.
I also noticed that you you start your properties in the lower case, best practices start them with caps. For example:
public HeatImage heatImage { get; set; }
...should be...
public HeatImage HeatImage { get; set; }
I hope this helps :)
Your view is expecting an IEnumerable of HeatingAreaViewModel and it seems as though your only passing a single instance.
Change this
#model IEnumerable<Project.Models.HeatingAreaViewModel>
to this
#model Project.Models.HeatingAreaViewModel
Then you'll be able to loop through the heatingArea and heatingImage properties like this
#foreach (var item in model.heatingArea)
{
//what ever you need to do here
}
If your problem is, how to iterate in one loop through the two lists of HeatingImage and HeatingArea, in your view, you have to redo your viewModel:
public class HeatingAreaViewModel
{
public HeatImage heatingImage { get; set; }
public HeatingArea heatingArea { get; set; }
}
and then the controller action:
public ActionResult Index()
{
heatingAreas = db.HeatingAreas.ToList();
heatingImages = db.HeatImages.ToList();
List<HeatingAreaViewModel> myModel = heatingAreas.Zip(heatingImages, (a, b) => new HeatingAreaViewModel {HeatingArea = a, HeatImage = b})
return View(mymodel);
}
Then the View will work, as it is.
However, I strongly advice against doing it this way. You would have to ensure, that corresponding elements in the two lists are in reality corresponding to each other. Which is very prone to errors. If there is an underlying logic that ties these two lists together (some relationship on database tables), I would use that one to join the lists/tables together, instead.
Simply your Model in the view doesn't matches what you sent from controller action, so I think you need to change your view to be like this:
#model HeatingAreaViewModel
#foreach (var item in Model.heatingImage)
{
#item.heatingArea
}
I would like to follow best MVC best practise for creating DropLists.
I have 3 Models (I have cut them down for the purposes of this)
Model One
Student
public int ID {get;set;}
public string Name {get;set}
public Site SiteID {get;set;}
Model Two
Site
public int ID {get;set;}
public string SiteName {get;set}
Model Three
VM
public int ID {get;set}
public student Students {get;set;}
public DateTime Date { get { return DateTime.Now; } }
public bool Criteria {get;set;}
In my VM view I am using EditorFor html helpers to populate my VM and Student Models. The site model is pre populated at the database seed.
I am looking for the best way to include a dropdownlist of sites on my VM view, that will map to my student model.
How to I correctly set up my models to achieve this?
In short, you want the DropDownListFor extension method and to put a List<Site> into the view model.
Here is a Fiddle that demonstrates your case. The Fiddle has more details. The nuts and bolts are here:
ViewModel - Add a List<Site>
public class MyViewModel
{
public MyViewModel()
{
this.Sites = new List<Site>();
}
public int ID { get; set;}
public Student Students { get; set; }
public DateTime Date { get { return DateTime.Now; } }
public bool Criteria { get; set; }
public List<Site> Sites { get; set; }
}
View - Use DropDownListFor
#Html.DropDownListFor(m => m.Sites,
new SelectList(Model.Sites, "ID", "SiteName"))
In psuedo-code, the above says
The Sites object in the model contains the properties to display.
Create a new SelectList using the Sites object in the model. Use the ID property as the data value and the SiteName property as the data text.
Create a drop down list based on the above info.
Controller
This just passes a seeded view model to the view.
public ActionResult Index()
{
var vm = SeedFromDatabase();
return View(vm);
}
private MyViewModel SeedFromDatabase()
{
var vm = new MyViewModel();
vm.Sites.Add(new Site(0, "one"));
vm.Sites.Add(new Site(1, "two"));
vm.Sites.Add(new Site(2, "three"));
return vm;
}
ViewModel
public class VM
{
public int ID {get;set}
public student Students {get;set;}
public SelectList SiteList {get;set;}
public int SiteID {get;set;}
public DateTime Date { get { return DateTime.Now; } }
public bool Criteria {get;set;}
}
Load View Action
public ActionResult LoadVMView(){
var model = new VM();
var items = GetSitesFromDatabase().Select(s => new SelectListItem(){
Text = s.SiteName,
Value = s.ID.ToString()
});
model.SiteList = new SelectList(items);
return View(model);
}
View:
#Html.DropDownListFor(m => m.SiteID, Model.SiteList)
On Post
[HttpPost]
public ActionResult LoadVMView(VM model){
var selecteSiteID = model.SiteID;
}
How can I bind a multi select dropdown list to a list property of a manually added intermediate table?
Classes
public class Department
{
public virtual int Id { get; set; }
public virtual IList<Lam> Lams { get; set; }
}
public class Person
{
public virtual int Id { get; set; }
public virtual IList<Lam> Lams { get; set; }
}
public class Lam
{
public virtual int Id { get; set; }
public virtual Department Department { get; set; }
public virtual Person Person { get; set; }
}
ViewModel
public class DepartmentCreateEditViewModel
{
public Department Department { get; set; }
public IList<Person> Persons { get; set; }
}
ActionResult
public ActionResult Create()
{
// Get all Persons
var persons = repositoryPerson.GetAll();
// Create ViewModel
var viewModel = new DepartmentCreateEditViewModel() { Department = new Department(), Persons = persons };
// Display View
return View(viewModel);
}
Create View
I tried to add a ListBox like this.
#Html.ListBoxFor(model => model.Department.Lams, new SelectList(Model.Persons, "Id", "Name"), new { #class = "form-controll" })
To save the object I want to get back a Department object
[HttpPost]
public ActionResult Create(Department department)
The binding from the dropdown (with persons) to the IList is not working. How am I supposed to do this? Is this even possible?
[Edit] Code after Erik's suggestion
Create ActionResult
[HttpPost]
public ActionResult Create(DepartmentCreateEditViewModel viewModelPostBack)
View
#Html.ListBoxFor(model => model.Department.Lams, new MultiSelectList(Model.Persons, "Id", "Name"), new { #class = "form-controll" })
What I get back:
viewModelPostBack
{EmpLaceMgmt.ViewModels.DepartmentCreateEditViewModel}
Department: {EmpLaceMgmt.Models.Department}
Persons: null
viewModelPostBack.Department
{EmpLaceMgmt.Models.Department}
Id: 0
Lams: Count = 0
The generated HTML looks like this
<select class="form-controll" id="Department_Lams" multiple="multiple" name="Department.Lams">
<option value="1">Example Person</option>
</select>
You have three problems. First, you are trying to bind to an IList<T>, but that won't work because the model binder won't know what kind of concrete object it should create to satisfy that... There are many objects that support IList<T>, so which one?
Secondly, You need to use a MultiSelectList and not a SelectList in your helper.
Third, you are posting back a different model type than you are using to create your pages. And that type has a very different structure. In the structure that you create your page with, your data is created with the naming of Department.Lams (because Department is a property of your ViewModel) but in your Post action takes a Department model which, the binder would be looking for an object simply called Lams, not Department.Lams.
So, convert your models to use a concrete type, such as List<Lam>, then post back to your ViewModel rather than Department, and extract the department out of the ViewModel, and finally change your helper to this:
#Html.ListBoxFor(model => model.Department.Lams,
new MultiSelectList(Model.Persons, "Id", "Name"), new { #class = "form-controll" })
I am using asp.net mvc 3, data annotations and auto mapper.
I want to have all my annotations on properties in my view model once the properties pass validation I use auto mapper to map it back to my domain object.
I have a viewmodel that has properties that I want to have a collection of since I want to generate a table from them. I also want to use them later to use as a form to add rows to this table.
So what do I do? How do I take these properties and make a collection of them?
public class UserViewModel()
{
[Required()]
public string UserName = {get; set;}
[Required()]
public string FirstName = {get; set;}
[Required()]
public string LastName = {get; set;}
}
I want to use these properties to generate my table and be used for a form.
The only thing I can think of is doing this
public class AddUserViewModel()
{
[Required()]
public string UserName = {get; set;}
[Required()]
public string FirstName = {get; set;}
[Required()]
public string LastName = {get; set;}
}
public class UserViewModel()
{
public List<User> Users {get; set;}
public AddUserViewModel {get; set;}
public UserViewModel()
{
Users = new List<Users>();
}
}
So basically I have it as a separate view model that is enclosed into another viewmodel that contains a list of Users(my domain model)
That way I use my domain model to generate the table and my AddUserViewModel for my adding of users.
Seems kinda redundant so I am not sure if there is a better way.
Edit
I have something like this
var viewModel = new UserViewModel();
List<Users> users= UserService.GetAllUsers();
viewModel = Mapper.Map<Users, UserViewModel>(users);
return View("Index", viewModel);
I also tried
var viewModel = new UserViewModel();
List<Users> users= UserService.GetAllUsers();
viewModel.AddUserViewModel = Mapper.Map<Users, AddUserViewModel>(users);
return View("Index", viewModel);
Edit 2
I have this and it compiles but I get this error
SomeViewModel viewModel = new SomeViewModel ();
List<User> users= userService.GetAllUsers();
viewModel.UserViewModel = Mapper.Map<List<User>, List<UserViewModel>>(users);
return View("Index", viewModel);
Trying to map Domain.User to ViewModels.UserViewModel.
Missing type map configuration or unsupported mapping.
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
Why would you want to return a list of domain objects in your view model? That's not what view models are supposed to be. View models should reference only other view models. So you have a nice UserViewModel which represents a user. Now you need to work with multiple users in your view, so either you pass an IEnumerable<UserViewModel> or if you need some other properties you design a view model for this:
public class UserViewModel
{
[Required]
public string UserName = { get; set; }
[Required]
public string FirstName = { get; set; }
[Required]
public string LastName = { get; set; }
}
public class SomeViewModel
{
public List<UserViewModel> Users { get; set; }
public string SomeOtherProperty { get; set; }
}
and now your controller action might look like this:
public ActionResult Foo()
{
SomeModel model = _repository.GetModel();
SomeViewModel viewModel = Mapper.Map<SomeModel, SomeViewModel>(model);
return View(viewModel);
}
Now inside your view you could simply use a display template for this Users property (Html.DisplayFor(x => x.Users)) to show a list of them.
UPDATE:
After seeing your update here's how to proceed in terms of good practices:
public ActionResult Foo()
{
IEnumerable<Users> users = _repository.GetUsers();
IEnumerable<UserViewModel> usersViewModel = Mapper
.Map<IEnumerable<Users>, IEnumerable<UserViewModel>>(users);
return View(usersViewModel);
}
I've also used an AutoMap attribute in a sample project which could simplify your code to this:
[AutoMap(typeof(IEnumerable<Users>), typeof(IEnumerable<UserViewModel>))]
public ActionResult Foo()
{
IEnumerable<Users> users = _repository.GetUsers();
return View(users);
}
This attribute will automatically run after the controller action and before the view is rendered and would use AutoMapper to replace the model with the corresponding view model.