MVC Entity Framework DropDownListFor<> - c#

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;
}

Related

How to name a class that populate select lists in the view model

I have view model class
public class IndexViewModel
{
public int ListOneId { get; set; }
public List<SelectListItem> ListOne { get; set; }
public int ListTwoId {get; set; }
public List<SelectListItem> ListTwo { get; set; }
public int ListThreeId {get; set; }
public List<SelectListItem> ListThree { get; set; }
public int ListFourId {get; set; }
public List<SelectListItem> ListFour { get; set; }
// ...
}
I can populate the view model in the controller class
[HttpGet]
public IActionResult Index()
{
var model = new IndexViewModel();
model.ListOne = _context.ListOne
.Select(x => new SelectListItem() { Text = x.Text, Value = x.Value })
.ToList()
// ...
return View(model);
}
But because the view model populating appears in multiple places, I would like to encapsulate it into its own class.
The question is, what is the suitable name for such a class?
It's not a builder, it's not a factory. Is it seeder, populator or helper? What common suffix can be used here?
Or should it be part of the ViewModel itself (for example with a method that has DbContext as an parameter)?
Update
The question is not about how to name the SelectList Provider but instead where to populate the view model (which class to call this SelectList provider from, whether it's repository, factory, DbContext or else) to minimize duplicates.
To be honest if you add a factory method then it easily becomes a factory pattern. That's what I would do personally
public static List<SelectListItem> GetTheList(int id) =>
id switch
{
1 => ListOne,
2 => ListTwo
//...
_ => throw new Exception("Please select a valid list")
};
//use it like:
List<SelectListItem> list = IndexSelectListFactory.GetTheList(1); //gets ListOne
Notice I have named it IndexSelectListFactory here, just add the suffix that matches the pattern you end up choosing, in this case "-Factory"
But if you do not want to do this, then just keep it simple like "IndexSelectLists"

MVC Two controllers One View

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.

ASP MVC 4: Request value of DropDownListFor

I have two classes many-to-many the first is "Anuncios" and the second "SubCategorias"
public class Anuncios {
public int AnuncioId {get;set;}
public string Titulo {get;set;}
public ICollection<SubCategorias> SubCategorias {get;set;}
}
public class SubCategorias {
public int SubCategoriaId {get;set;}
public string Nome {get;set;}
public ICollection<Anuncios> Anuncios {get;set;}
}
In DAL layer I did method to save the "Anuncio" in DB.
public void Salvar(Anuncio entidade) {
entidade.SubCategorias = entidade.SubCategorias.Select(subcat => _contexto.SubCategorias.FirstOrDefault(x => x.SubCategoriaId == subcat.SubCategoriaId)).ToList();
_contexto.Anuncios.Add(entidade);
_contexto.SaveChanges();
}
I Create the Action "Create":
private readonly Context _ctx = new Context();
public ActionResult Create()
{
var model = new Anuncios {SubCategorias = _ctx.SubCategorias.ToList()};
return View(model);
}
In View I made DropDownList with "SubCategorias":
#Html.LabelFor(model => model.SubCategorias)
#Html.DropDownListFor(model => model.SubCategorias, new SelectList(Model.SubCategorias, "SubCategoriaId", "Nome"))
The DropDownListFor is populated with sucess..
Fine....
But when submit form the value selected in DropDownListFor not pass to method Create. The anuncio.SubCategorias is null!
private readonly AnunciosDal _anuncio = new AnunciosDal();
[HttpPost]
public ActionResult Create(Anuncio anuncio)
{
_anuncio.Salvar(anuncio);
return View(anuncio);
}
I have sought in various forums the solution, but could not find
Somebody help me?!
Sorry about my english rs...
Thank You!
Fabrício Oliveira
The first parameter of DropDownListFor needs to be the object holding the selected value, where the second parameter contains the list:
#Html.DropDownListFor(model => model.SOME_ID_FOR_SELECTED_VALUE,
new SelectList(Model.SubCategorias, "SubCategoriaId", "Nome"))
Currently the example you have also maps the same list as the first property. You should use an ID like #Maess suggested, and then bind it via:
#Html.DropDownListFor(model => model.SubCategoriaID, new SelectList(Model.SubCategorias, "SubCategoriaId", "Nome"))
Selecting a value will then post it back to the server to this SubCategoriaID field.
You need to have another property to store the selected value from the dropdown. It is best if you create a viewmodel with properties which are needed for your view.
public class CreateAnuncios
{
public string Title {set;get;}
public int SelectedSubCategoryId {set;get;}
public List<SelectListItem> SubCategories {set;get;}
public CreateAnuncios()
{
this.SubCategories = new List<SelectListItem>();
}
}
Now in your create action, create an object of this view model, Fill the SubCategories property and send to the view.
public ActionResult Create()
{
var vm=new CreateAnuncios();
vm.SubCategories = ctx.SubCategorias
.Select(s=> new SelectListItem
{ Value = s.SubCategoriaId .ToString(),
Text=s.Nome}).ToList();
return View(vm);
}
Your create view should be strongly typed to the CreateAnuncios class
#model YourNameSpaceHere.CreateAnuncios
#using(Html.Beginform())
{
#Html.TextBoxFor(s=>s.Title)
#Html.DropdownListFor(s=>s.SelectedSubCategoryId,Model.SubCategories,"Select")
<input type="submit" />
}
Now when user posts the form, Read the Properties of the posted model and use that to save to db.
[HttpPost]
public ActionResult Create(CreateAnuncios model)
{
//Check for model.Title and model.SelectedSubCategoryId and use it to save
// to do :Save and redirect
}
You need to provide a collection of SelectListItems that populate your DropDownList as well as a property to hold the currently selected SelectListItems' Id (which will be posted back to the server):
public class Anuncios {
public int AnuncioId {get;set;}
public string Titulo {get;set;}
public ICollection<SubCategorias> SubCategorias {get;set;}
public int SelectedSubCategoryId {get;set;}
public IEnumerable<SelectListItem> SubCategoryListItems {get;set;}
}
then, present them to the user via:
#html.DropDownListfor(x => x.SelectedSubCategoryId, Model.SubCategoryListItems)
The SubCategoryListItems will have to be populated from the server, typically something like this:
this.SubCategoryListItems = this.SubCategorias.Select(x => new SelectListItem { Text = x.Name, Value = x.Id });
You need an id property for the SubCategoria you have it in your binding, but not in the model
public class Anuncios {
public int AnuncioId {get;set;}
public string Titulo {get;set;}
public ICollection<SubCategorias> SubCategorias {get;set;}
public int SubCategoriaId{get;set;}
}

Following correct practise when building view models

I have run into a caveat with regards to my approach to ASP.NET MVC and viewmodels. Essentially what I do is build a viewmodel in controller/action which merges models together and then passes it to the view.
[HttpGet]
public ActionResult MyAction1()
{
List<StaffModel> staffList = new List<StaffModel>();
var qryStaff = context.Staff.Select(c => new { c.ID, c.name});
foreach (var item in qryStaff )
{
StaffModel myStaffViewModel = new StaffModel
{
ID = item.ID, Name = item.Name
};
staffList.Add(myStaffViewModel );
}
So I do the above process and also do it with employees, exactly the same and then put it into employeeList. I then create my viewModel as the view.
EmployeeStaffViewModel viewModel = new EmployeeStaffViewModel
{
Staff = staffList,
Employee = employeeList
};
I then return the view. I have used employee & staff as an example. I actually have more models I add to the viewModel EmployeeStaffViewModel . It's getting quite big all within the controller action. Should I be creating a ViewModel as a class and then instantiating it in my controller so all the linq and foreach goes in the Model. Therefore I can use it in another controller action.
Thank you for any advice. Will be greatly received.
Lets say you have 3 classes in your ~/Models folder
StaffModel.cs
public class StaffModel
{
public int ID { get; set; }
public string Name { get; set; }
public static Func<Staff, StaffModel> Project = item => new StaffModel
{
ID = item.ID,
Name = item.Name
};
}
EmployeeModel.cs
public class EmployeeModel
{
public int ID { get; set; }
public string Name { get; set; }
public static Func<Employee, EmployeeModel> Project = item => new EmployeeModel
{
ID = item.ID,
Name = item.Name
};
}
EmployeeStaffViewModel.cs
public class EmployeeStaffViewModel
{
public EmployeeStaffViewModel()
{
Staff = new List<StaffModel>();
Employee = new List<EmployeeModel>();
}
public List<StaffModel> Staff { get; set; }
public List<EmployeeModel> Employee { get; set; }
}
The StaffModel and EmployeeModel both have a static Func<> that will map your db entity to your models. These Funcs can be used in your linq queries and expressions which you'll see below.
Your controller action is where you will retrieve your entities from your context. You can simplify your code to not have as many lines as you do.
MyController.cs
[HttpGet]
public ActionResult MyAction1()
{
var model = new EmployeeStaffViewModel();
model.Staff = context.Staff.Select(StaffModel.Project); //Select Staff to StaffModel List
model.Employee = context.Employee.Select(EmployeeModel.Project); //Select Employee to EmployeeModel List
return View(model);
}

How to do Lists in a view model? + dataannotations

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.

Categories

Resources