How do I handle a many to many object mapping in the view and controller in the context of users and roles?
I used entity framework to map to pure POCOs like this:
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public List<User> Users { get; set; }
}
public class User
{
public int UserId { get; set; }
public List<Role> Roles { get; set; }
}
In my view, I'd like to add a User to a role using checkboxes. I list all the roles then check one to add the user to that role. How do I handle this?
I would start by designing a view model for this scenario:
public class UserRolesViewModel
{
public int UserId { get; set; }
public IEnumerable<RoleViewModel> Roles { get; set; }
}
public class RoleViewModel
{
public int RoleId { get; set; }
public bool InRole { get; set; }
public string RoleName { get; set; }
}
Then a roles controller:
public class RolesController : Controller
{
public ActionResult Edit(int userId)
{
// TODO: Use a repository to fetch the roles associated to the given
// user id and then AutoMapper to map your model POCOs
// to a UserRolesViewModel
var model = new UserRolesViewModel
{
UserId = userId,
Roles = new[]
{
new RoleViewModel { RoleId = 1, InRole = false, RoleName = "Role 1" },
new RoleViewModel { RoleId = 2, InRole = true, RoleName = "Role 2" },
new RoleViewModel { RoleId = 3, InRole = true, RoleName = "Role 3" }
}
};
return View(model);
}
[HttpPut]
public ActionResult Update(UserRolesViewModel model)
{
// Here you will get the view model back containing the
// user id and the selected roles
// TODO: use AutoMapper to map back to a POCO and
// invoke the repository to update the database
return RedirectToAction("Edit");
}
}
then the Edit view (~/Views/Roles/Edit.cshtml):
#model YourAppName.Models.UserRolesViewModel
#{
ViewBag.Title = "Edit user roles";
}
<h2>Roles for user #Model.UserId</h2>
#using (Html.BeginForm("Update", "Roles"))
{
#Html.HttpMethodOverride(HttpVerbs.Put)
#Html.HiddenFor(x => x.UserId)
#Html.EditorFor(x => x.Roles)
<input type="submit" value="update roles" />
}
and finally the corresponding editor template (~/Views/Roles/EditorTemplates/RoleViewModel.cshtml):
#model YourAppName.Models.RoleViewModel
<div>
#Model.RoleName
#Html.HiddenFor(x => x.RoleId)
#Html.CheckBoxFor(x => x.InRole)
</div>
Related
I have been scratching my head for a whole night on an issue I can do quickly using ajax/jquery and stored procedures. I want to
1) Populate a drop down list from values obtained from a database table using Entity Framework and view model. I DO NOT WANT TO USE VIEWBAG OR VIEWDATA. Any help appreciated.
2) How can I generate a Create View using the View Model with the all the default fields ? The scaffholding works on a model but not on a view model ?
MY MODELS
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string City { get; set; }
public string Level { get; set; }
public int DepartmentId { get; set; }
}
public class Grade
{
public int ID { get; set; }
public string Level { get; set; }
}
View Model
public class GradeSelectListViewModel
{
public Employee Employee { get; set; }
public IEnumerable<SelectListItem> Grades { get; set; }
public GradeSelectListViewModel(Employee employee, IEnumerable grades)
{
Employee = employee;
Grades = new SelectList(grades, "Grade", "Name", employee.Level);
}
}
MY CONTEXT CLASS
public class EmployeeContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Grade> Grades { get; set; }
}
MY CONTROLLER
public ActionResult Edit (int? id)
{
using (var db = new EmployeeContext())
{
var model = new GradeSelectListViewModel(db.Employees.Find(id), db.Grades);
//model.Employee = db.Employees.Single(x => x.EmployeeID == id);
model.Grades = db.Grades.ToList().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Level
});
return View(model);
}
}
MY RAZOR PAGE CSHTML
#model MVCDemo.ViewModels.GradeSelectListViewModel
....
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
....
#Html.DropDownListFor(x => Model.Employee.Level,
new SelectList(Model.Grades, "ID", "Level"),
"Select Level")
....
<input type="submit" value="Create" class="btn btn-default" />
}
The main issue is that in the view you have new SelectList(Model.Grades, "ID", "Level") but Grades is IEnumerable<SelectListItem> and SelectListItem does not contain properties named ID and Level.
However there are a a few other issues with your code. First a view model should not contain a data model, and instead your view model should be
public class GradeSelectListViewModel
{
public int? ID { get; set; } // make this ID so you do not need an input for it
public string Name { get; set; }
.... // other properties of Employee that your editing
[Required(ErrorMessage = "..")]
public int? Level { get; set; } // make value types nullable to protect against under-posting attacks
public IEnumerable<SelectListItem> Grades { get; set; }
}
and add display and validation attributes as required. Note that I deleted the constructor (you don't seem to be using it, but if you did, then you also need to include a parameter-less constructor, otherwise an exception will be thrown when submitting to the POST method. I also assume that Level should be typeof int since you binding to the int ID property of Grade.
The the code in your GET method should be
Employee employee = db.Employees.Find(id);
var model = new GradeSelectListViewModel()
{
ID = employee.EmployeeID,
Name = employee.Name,
Level = employee.Level, // convert to int?
....
Grades = db.Grades.Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Level
})
};
return View(model);
and in the view
#Html.DropDownListFor(x => Model.Level, Model.Grades, "Select Level")
Note also that in the POST method, your need to reassign the SelectList if you return the view because ModelState is invalid.
You can use the following approach that populates three DropDownListFor at the same View:
ViewModel:
public class GroupViewModel
{
public IEnumerable<SelectListItem> Schedules { get; set; }
public int ScheduleId { get; set; }
public IEnumerable<SelectListItem> Labs { get; set; }
public int LabId { get; set; }
public IEnumerable<SelectListItem> Terms { get; set; }
public int TermId { get; set; }
}
Controller:
public ActionResult Create()
{
//Populate DropDownList binding values
var model = new GroupViewModel
{
//Preselect the Lab with id 2
//LabId = 2,
Labs = repository.Labs.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
}),
Terms = repository.Terms.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
}),
Schedules = repository.Schedules.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
})
};
return View("Create", model);
}
View:
#Html.DropDownListFor(m => m.LabId, new SelectList(Model.Labs, "Value", "Text"),
"Select", new { #class = "selectpicker" })
#Html.DropDownListFor(m => m.ScheduleId, new SelectList(Model.Schedules, "Value", "Text"),
"Select", new { #class = "selectpicker" })
#Html.DropDownListFor(m => m.TermId, new SelectList(Model.Terms, "Value", "Text"),
"Select", new { #class = "selectpicker" })
I have a model called club and each club has a virtual list property for the members of that club. I am lost as to how to add more members to that list and then save it to my database.
public class Club
{
[Key]
public int ClubID { get; set; }
public string ClubName { get; set; }
public string ClubDescription { get; set; }
//List of Members that are members of this Club
public virtual List<ClubMember> ClubMembers { get; set; }
}//end Club
This is the ClubMember model:
public class ClubMember
{
[Key]
public int MemberId { get; set; }
//First Name
[Display(Name = "First Name")]
public string MemberFName { get; set; }
//Last Name
[Required(ErrorMessage = "You must enter a Last Name")]
[Display(Name = "Last Name")]
public string MemberLName { get; set; }
[Display(Name = "Member Name")]
public string MemberName { get; set; }
public string MemberEmail { get; set; }
//Foreign Key for Club
public int ClubID { get; set; }
[ForeignKey("ClubID")]
public virtual Club Club { get; set; }
}
I am using a wrapper model to get a list of the selected ids for the members that the user wishes to add but I'm not sure if this is needed:
public class NewMemberList //Class used when adding new members to the members list of a club
{
public List<ClubMember> NewMembers { get; set; }
public List<int> SelectedIDs { get; set; }
}
This is what I currently have in my view for adding a member, it just displays a drop-down list with a list of members and a submit button
#model ultimateorganiser.Models.NewMemberList
#{
ViewBag.Title = "Add Members";
}
#using (Html.BeginForm(#Model.SelectedIDs))
{
#Html.AntiForgeryToken()
#Html.ListBoxFor(m => m.SelectedIDs, new MultiSelectList(Model.NewMembers, "UserId", "UserFName", Model.SelectedIDs))
<input type="submit" value="save" />
}
This is the controller method I have. It is not finished as I do not know how to handle the post part so that it gets the list of selected ids and adds all of the data for that member to the members list in the club:
[HttpGet]
public ActionResult AddMembers(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Club club = db.Clubs.Find(id);
if (club == null)
{
return HttpNotFound();
}
List<ClubMember> CurrentMembers = club.ClubMembers;
List<ClubMember> MembersList = new List<ClubMember>();
MembersList = db.ClubMembers.ToList();
ViewBag.CurrentMembersList = CurrentMembers;
return View(new NewMemberList() { NewMembers = MembersList });
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddMembers([Bind(Include = "SelectedIDs")] Club club)
{
if (ModelState.IsValid)
{
//Get selected members and add them to Members list for the club
return RedirectToAction("Index");
}
return View(club);
}
If you have questions or would like to see more of my code just ask.
Your view model should store the ClubId as well since you are adding the new members to a specific Club.
public class AddMembersToClub
{
public string Name { set;get;}
public int ClubId { set;get;}
public List<SelectListItem> Members { set;get;}
public int[] SelectedMembers { set;get;}
}
And in your GET action,
public ActionResult AddMembers(int id)
{
var club = db.Clubs.Find(id);
if (club == null)
{
return HttpNotFound();
}
var vm = new AddMembersToClub { ClubId=id , Name = club.ClubName };
//Here I am getting all the members, If you want a subset, update the LINQ query
vm.Members = db.ClubMembers
.Select(x=> new SelectListItem { Value = x.MemberId.ToString(),
Text=x.MemberFName }).ToList();
return View(vm);
}
and in your view, which is strongly typed to our AddMembersToClub view model. You need to keep the ClubId in a hidden form field as we need that in the HttpPost action.
#model AddMembersToClub
#using(Html.BeginForm())
{
<p>Adding members to #Model.Name</p>
#Html.HiddenFor(s=>s.ClubId)
#Html.ListBoxFor(s => s.SelectedMembers, Model.Members)
<input type="submit" />
}
And in your HttpPost action, Read the SelectedMembers property which is an int array storing the Id's of selected members and using the Id, get the Member entity and udpate the ClubId property.
[HttpPost]
public ActionResult AddMembers(AddMembersToClub model)
{
foreach(var item in model.SelectedMembers)
{
var member = db.ClubMembers.FirstOrDefault(s=>s.MemberId==item);
if(member!=null)
{
member.ClubId = model.ClubId;
}
db.SaveChanges();
}
return ReidrectToAction("Index");
}
Model:
public class PublishedSongViewModel
{
public int Id { get; set; }
[Required(AllowEmptyStrings = false)]
public string SongName { get; set; }
//...
[Required]
public IEnumerable<string> Category { get; set; }
}
public class CategoryViewModel
{
public short Id { get; set; }
public string Title { get; set; }
public virtual ICollection<SongCategoryViewModel> SongCategory { get; set; }
}
public class SongCategoryViewModel
{
public int Id { get; set; }
[Required]
public int PublishedSongId { get; set; }
[Required]
public short CategoryId { get; set; }
}
View:
#model IList<PublishedSongViewModel>
#using (Html.BeginForm("PublishMusic", "Publish", FormMethod.Post, new { #enctype = "multipart/form-data", #id = "form-upload" }))
{
#Html.DropDownListFor(x => Model[i].Category, new SelectList(//Categories list here), new { #class = "form-control dl_Categories ", Multiple = "Multiple" })
}
Controller:
[HttpPost]
public ActionResult PublishMusic(IEnumerable<PublishedSongViewModel> songDetails)
{
if (songDetails != null)
{
IEnumerable<PublishedSongViewModel> savedSongs = (IEnumerable<PublishedSongViewModel>)(Session["UserSongs"]);
var lookupDetails = songDetails.ToDictionary(song => song.Id, song => song);
if (savedSongs != null)
{
foreach (var publishedSong in savedSongs)
{
var key = publishedSong.Id;
if (lookupDetails.ContainsKey(key))
{
var details = lookupDetails[key];
publishedSong.SongName = details.SongName;
}
db.SongCategories.Add(new SongCategoryViewModel { PublishedSongId = key, CategoryId = //categories id that user typed in on editorFor});
db.PublishedSongs.Add(publishedSong);
db.SaveChanges();
}
}
}
return View("Index");
}
I'v filled CategoryViewModel table up with data in my SQL.
1) How do I get the titles of CategoryViewModel and pass them in the SelectList(//Here) parameter in my viewmodel?
2) In the PublishMusic Action, how do I get the CategoryId for the SongCategoryViewModel from the one or more categories that the user selected from songDetails.Category?
I am not sure if I am on the right track with this. basically the categories are like tags, the user can select more than one. I'v also cut out unessential code to make easier to read.
I have MVC3 web application where we need to populate radio button list with validation. My Model is something like this:
public class EmployeesViewModel
{
public List<Employee> listEmployee { get; set; } //To persist during post
public IEnumerable<SelectListItem> selectListEmployee { get; set; }
[Required]
public Employee selectedEmployee { get; set; }
}
public class Employee
{
public int ID {get; set;}
public string Name {get; set}
public string Department {get; set}
}
I need to populate radiobutton list something like below:
Employee1ID - Employee1Name - Employee1Department // id - name - department
Employee2ID - Employee2Name - Employee2Department
Employee3ID - Employee3Name - Employee3Department
Selected Employee should be stored into "selectedEmployee" field. What is the best or clean way to populate these radio button List in MVC3?
Note: Mainly Looking for two task:
1. storing "Employee" object in each "Input" radio button tag, so that selected employee will be saved to "selectedEmployee" field
2. Best way to mark "Employee" object as required field
Much appreciate your help!
Thanks,
Here's what I would recommend you. Start with a clean view model, one that really expresses what the view contains as information:
public class EmployeesViewModel
{
public List<EmployeeViewModel> ListEmployee { get; set; }
[Required]
public int? SelectedEmployeeId { get; set; }
}
public class EmployeeViewModel
{
public int ID { get; set; }
public string Label { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new EmployeesViewModel
{
ListEmployee = GetEmployees()
};
return View(model);
}
[HttpPost]
public ActionResult Index(EmployeesViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid, the user didn't select an employee
// => refetch the employee list from the repository and
// redisplay the view so that he can fix the errors
model.ListEmployee = GetEmployees();
return View(model);
}
// validation passed at this stage
// TODO: model.SelectedEmployeeId will contain the id
// of the selected employee => use your repository to fetch the
// actual employee object and do something with it
// (like grant him the employee of the month prize :-))
return Content("thanks for submitting", "text/plain");
}
// TODO: This doesn't belong here obviously
// it's only for demonstration purposes. In the real
// application you have a repository, use DI, ...
private List<EmployeeViewModel> GetEmployees()
{
return new[]
{
new EmployeeViewModel { ID = 1, Label = "John (HR)" },
new EmployeeViewModel { ID = 2, Label = "Peter (IT)" },
new EmployeeViewModel { ID = 3, Label = "Nathalie (Sales)" },
}.ToList();
}
}
and finally a view:
#model EmployeesViewModel
#using (Html.BeginForm())
{
#Html.ValidationMessageFor(x => x.SelectedEmployeeId)
#foreach (var employee in Model.ListEmployee)
{
<div>
#Html.RadioButtonFor(x => x.SelectedEmployeeId, employee.ID, new { id = "emp" + employee.ID })
#Html.Label("emp" + employee.ID, employee.Label)
</div>
}
<input type="submit" value="OK" />
}
I read that EditorTemplates are loaded automatically, but from asp.net mvc 2 and now 3 with razor, I still cant get this to work.
My model looks like this:
public class RoleViewModel
{
public int RoleId { get; set; }
public bool InRole { get; set; }
public string RoleName { get; set; }
}
public class UserViewModel
{
public User User { get; set; }
public IEnumerable<RoleViewModel> Roles { get; set; }
}
My view looks like this:
~/Views/Roles/Edit.cshtml
#model Project.Web.ViewModel.UserViewModel
#using (Html.BeginForm()) {
#Html.EditorFor(model => model.Roles)
<!-- Other stuff here -->
}
~/Views/Roles/EditorTemplates/RoleViewModel.cshtml
#model Project.Web.ViewModel.RoleViewModel
#foreach (var i in Model)
{
<div>
#i.RoleName
#Html.HiddenFor(model => i.RoleId)
#Html.CheckBoxFor(model => i.InRole)
</div>
}
If i move the content from the EditorTemplate to the actual page, then it works, it shows the checkbox, etc. But with this current setup, all that shows up is the count of the number of roles.
What am I doing wrong?
~/Views/Roles/EditorTemplates/RoleViewModel.cshtml
#model MvcApplication16.Controllers.RoleViewModel
<div>
#Model.RoleName
#Html.HiddenFor(m => m.RoleId)
#Html.CheckBoxFor(m => m.InRole)
</div>
~/Views/Roles/Edit.cshtml
#model MvcApplication16.Controllers.UserViewModel
#using (Html.BeginForm()) {
#Html.EditorFor(m => m.Roles)
<!-- Other stuff here -->
}
Models
public class UserViewModel {
public User User { get; set; }
public IEnumerable<RoleViewModel> Roles { get; set; }
}
public class RoleViewModel {
public int RoleId { get; set; }
public bool InRole { get; set; }
public string RoleName { get; set; }
}
public class User {
public string Name { get; set; }
}
Controller
public ActionResult Edit() {
return View(
new UserViewModel() {
User = new User() { Name = "Test" },
Roles = new List<RoleViewModel>() {
new RoleViewModel() {
RoleId = 1,
InRole = true,
RoleName = "Test Role" }}
});
}
The above code works just fine. Compare it with yours and see if you see anything amiss :)