ASP.NET MVC 5 & Entity Framework many to many relationship - c#

I have a many-to-many relationship between User and Task model. I want to assign tasks to users when I am creating a new task. Because of that I created UserViewModel and CheckBoxView model. But now I can just assign tasks to users when I am editing a user table. I know I have to change UserController's Create action but I do not know how.
Here is my code:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public virtual ICollection<UserToTask> UserToTasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<UserToTask> UserToTasks { get; set; }
}
public class UserToTask
{
public int Id { get; set; }
public int UserId { get; set; }
public int TaskId { get; set; }
public virtual User User { get; set; }
public virtual Task Task { get; set; }
}
public class CheckBoxViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
public class UserViewModel
{
public int UserId { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public List<CheckBoxViewModel> Tasks { get; set; }
}
User Controller:
public class UsersController : Controller
{
private MyModel db = new MyModel();
// GET: Users
public ActionResult Index()
{
return View(db.Users.ToList());
}
// GET: Users/Create
public ActionResult Create()
{
return View();
}
// POST: Users/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Surname")] User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
// GET: Users/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User user = db.Users.Find(id);
if (user == null)
{
return HttpNotFound();
}
var result = from a in db.Tasks
select new
{
a.Id,
a.Title,
Checked = (from ab in db.UserToTasks
where (ab.UserId == id) & (ab.TaskId == a.Id)
select ab).Any()
};
var MyViewModel = new UserViewModel();
MyViewModel.UserId = id.Value;
MyViewModel.Name = user.Name;
MyViewModel.Surname = user.Surname;
var MyCheckBoxList = new List<CheckBoxViewModel>();
foreach (var item in result)
{
MyCheckBoxList.Add(new CheckBoxViewModel{Id = item.Id, Name = item.Title, Checked = item.Checked});
}
MyViewModel.Tasks = MyCheckBoxList;
return View(MyViewModel);
}
// POST: Users/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel user)
{
if (ModelState.IsValid)
{
var MyUser = db.Users.Find(user.UserId);
MyUser.Name = user.Name;
MyUser.Surname = user.Surname;
foreach (var item in db.UserToTasks)
{
if (item.UserId == user.UserId)
{
db.Entry(item).State = EntityState.Deleted;
}
}
foreach (var item in user.Tasks)
{
if (item.Checked)
{
db.UserToTasks.Add(new UserToTask() {UserId = user.UserId, TaskId = item.Id});
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
}
Edit view:
#model ItalianCoach.Models.UserViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.UserId)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Surname, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Surname, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Surname, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Tasks, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#for (int i = 0; i < Model.Tasks.Count(); i++)
{
#Html.EditorFor(m => m.Tasks[i].Checked)
#Html.DisplayFor(m => m.Tasks[i].Name)<br/>
#Html.HiddenFor(m => m.Tasks[i].Name)
#Html.HiddenFor(m => m.Tasks[i].Id)
}
</div>
</div>
</div>
}

You do not need the UserToTask class. The relationships between them should be with the object classes themselves.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int TaskId { get; set; }
public virtual Task Task { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Title { get; set; }
}
You add in the TaskId as foreign key in the User class. Each User will have a collection of Tasks (which may or may not be empty - so be careful with your constructors).
To find all the Users for a particular Task - you use a database query on Users -> where TaskId == Task.Id.
You'll need to sort out the xaml, controllers, etc, to fit the new model.

Related

CheckBoxList using ASP.NET MVC 5

I'm having trouble understanding how to retrieve data from my Device table in my database to a CheckBoxList.
I believe I have the Models, ViewModel and Database setup properly but I'm just not sure what to do next. For now, I would simple like to learn how to retrieve the data from my table to a CheckBoxList.
Customer Model
public class Customer
{
public int CustId { get; set; }
public string CustDisplayName { get; set; }
public string CustFirstName { get; set; }
public string CustLastName { get; set; }
public string CustCompanyName { get; set; }
public string CustAddress { get; set; }
public string CustPhoneNumber { get; set; }
public string CustMobileNumber { get; set; }
public string CustEmailAddress { get; set; }
public int StId { get; set; }
public State State { get; set; }
}
State Model
public class State
{
public int StId { get; set; }
public string StAbbr { get; set; }
public List<Customer> Customers { get; set; }
}
Device Model
public class Device
{
public int DevId { get; set; }
public string DevType { get; set; }
public bool isChecked { get; set; }
}
CustomerDevice Model
public class CustomerDevice
{
public int CustId { get; set; }
public int DevId { get; set; }
public Customer Customer { get; set; }
public Device Device { get; set; }
}
CustomerViewModel
public class CustomerFormViewModel
{
public int CustId { get; set; }
[Required(ErrorMessage = "Enter Display Name")]
[Display(Name = "Display Name")]
[StringLength(100)]
public string CustDisplayName { get; set; }
[Display(Name = "First Name")]
[StringLength(50)]
public string CustFirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(50)]
public string CustLastName { get; set; }
[Display(Name = "Company Name")]
[StringLength(50)]
public string CustCompanyName { get; set; }
[Display(Name = "Phone Number")]
[DataType(DataType.PhoneNumber)]
[StringLength(12)]
public string CustPhoneNumber { get; set; }
[Display(Name = "Mobile Number")]
[DataType(DataType.PhoneNumber)]
[StringLength(12)]
public string CustMobileNumber { get; set; }
[Display(Name = "Email Address")]
[DataType(DataType.EmailAddress)]
[StringLength(320)]
public string CustEmailAddress { get; set; }
[Required(ErrorMessage = "Enter Address")]
[Display(Name = "Address")]
[StringLength(100)]
public string CustAddress { get; set; }
[Required(ErrorMessage = "Select State")]
[Display(Name = "State")]
public int StId { get; set; }
public IEnumerable<State> States { get; set; }
}
CustomerController
public class CustomerController : Controller
{
private CoreWebAppContext _context;
public CustomerController(CoreWebAppContext context)
{
_context = context;
}
// GET: /<controller>/
public IActionResult Index()
{
return View(_context.Customers.ToList());
}
public ActionResult Create()
{
var states = _context.States.ToList();
var viewModel = new CustomerFormViewModel
{
States = states
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CustomerFormViewModel vm)
{
if (ModelState.IsValid)
{
var customer = new Customer();
{
customer.CustDisplayName = vm.CustDisplayName;
customer.CustFirstName = vm.CustFirstName;
customer.CustLastName = vm.CustLastName;
customer.CustCompanyName = vm.CustCompanyName;
customer.CustAddress = vm.CustAddress;
customer.CustPhoneNumber = vm.CustPhoneNumber;
customer.CustMobileNumber = vm.CustMobileNumber;
customer.CustEmailAddress = vm.CustEmailAddress;
customer.StId = vm.StId;
}
_context.Customers.Add(customer);
_context.SaveChanges();
return RedirectToAction("Index");
}
else
{
vm.States = _context.States.ToList();
return View(vm);
}
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var customervm = new CustomerFormViewModel();
{
Customer customer = _context.Customers.SingleOrDefault(c => c.CustId == id);
if (customer == null)
{
return NotFound();
}
customervm.CustId = customer.CustId;
customervm.CustDisplayName = customer.CustDisplayName;
customervm.CustFirstName = customer.CustFirstName;
customervm.CustLastName = customer.CustLastName;
customervm.CustCompanyName = customer.CustCompanyName;
customervm.CustAddress = customer.CustAddress;
customervm.CustPhoneNumber = customer.CustPhoneNumber;
customervm.CustMobileNumber = customer.CustMobileNumber;
customervm.CustEmailAddress = customer.CustEmailAddress;
// Retrieve list of States
var states = _context.States.ToList();
customervm.States = states;
// Set the selected state
customervm.StId = customer.StId;
}
return View(customervm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(CustomerFormViewModel vmEdit)
{
if (ModelState.IsValid)
{
Customer customer = _context.Customers.SingleOrDefault(c => c.CustId == vmEdit.CustId);
if (customer == null)
{
return NotFound();
}
customer.CustDisplayName = vmEdit.CustDisplayName;
customer.CustFirstName = vmEdit.CustFirstName;
customer.CustLastName = vmEdit.CustLastName;
customer.CustCompanyName = vmEdit.CustCompanyName;
customer.CustAddress = vmEdit.CustAddress;
customer.CustPhoneNumber = vmEdit.CustPhoneNumber;
customer.CustMobileNumber = vmEdit.CustMobileNumber;
customer.CustEmailAddress = vmEdit.CustEmailAddress;
customer.StId = vmEdit.StId;
_context.Entry(customer).State = EntityState.Modified;
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(vmEdit);
}
}
Create View
<div class="form-group">
#Html.LabelFor(c => c.CustDisplayName)
#Html.TextBoxFor(c => c.CustDisplayName, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.CustDisplayName)
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustFirstName)
#Html.TextBoxFor(c => c.CustFirstName, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustLastName)
#Html.TextBoxFor(c => c.CustLastName, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustCompanyName)
#Html.TextBoxFor(c => c.CustCompanyName, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustAddress)
#Html.TextBoxFor(c => c.CustAddress, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.CustAddress)
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustPhoneNumber)
#Html.TextBoxFor(c => c.CustPhoneNumber, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.CustPhoneNumber)
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustMobileNumber)
#Html.TextBoxFor(c => c.CustMobileNumber, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.CustMobileNumber)
</div>
<div class="form-group">
#Html.LabelFor(c => c.CustEmailAddress)
#Html.TextBoxFor(c => c.CustEmailAddress, new { #class = "form-control" })
#Html.ValidationMessageFor(c => c.CustEmailAddress)
</div>
<div class="form-group">
#Html.LabelFor(s => s.StId)
#Html.DropDownListFor(s => s.StId, new SelectList(Model.States, "StId", "StAbbr"), "", new { #class = "form-control" })
#Html.ValidationMessageFor(s => s.StId)
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
Make another ViewModel class called IndexViewModel and put this on it:
public class IndexViewModel
{
public List<Customer> Customers { get; set; }
public List<Device> Devices { get; set; }
}
Here is your controller Action. Return the new IndexViewModel instead:
// GET: /<controller>/
public IActionResult Index()
{
return View(_context.Customers.ToList());
}
to
// GET: /<controller>/
public IActionResult Index()
{
var model = new IndexViewModel();
model.Customers = _context.Customers.ToList();
model.Devices = _context.Devices.ToList();
return View(model);
}
In your view, simply iterate the Device list:
#{
for(int i = 0; i < Model.Devices.Count(); i++)
{
var checkboxAttributes = Model.Devices[i].isChecked == true ?
(object) new { #class = "checkbox", #checked = "checked" } :
(object) new { #class = "checkbox" };
#Html.CheckBoxFor(model => model.Devices[i].DevType, checkboxAttributes)
#Html.LabelFor(model => model.Devices[i].DevType, new { #class = "label" })
}
}
Update 1
I'm sorry about the previous one but this is the correct version: It uses bootstrap style for the checkbox:
#{
for (int i = 0; i < Model.Devices.Count(); i++)
{
<div class="checkbox">
<label>
#Html.HiddenFor(model => model.Devices[i].DevId)
#Html.CheckBoxFor(model => model.Devices[i].isChecked)
#Html.DisplayFor(model => model.Devices[i].DevType)
#Html.HiddenFor(model => model.Devices[i].DevType)
</label>
</div>
}
}
You can use something like this, so you can reuse it for different models.
CheckboxViewModel:
public class CheckboxViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
Extension method:
public static IEnumerable<CheckboxViewModel> CreateCheckboxList<T>(this List<T> entities, Func<T, object> value, Func<T, object> text, Func<T, bool> isChecked)
{
return entities.Select(x => new CheckboxViewModel
{
Id = (int)value(x),
Name = text(x).ToString(),
Checked = isChecked(x)
});
}
Using example:
result = devices.CreateCheckboxList(x => x.Id, x => x.Name, x => selectedDevices.Contains(x.Id)).ToList();
#for (var deviceIndex = 0; deviceIndex < Model.Devices.Count(); deviceIndex ++)
{
<div class="form-group">
<div class="checkbox checkbox-primary">
#Html.HiddenFor(model => model.Devices[deviceIndex ].Id)
#Html.CheckBoxFor(model => model.Devices[deviceIndex ].Checked)
#Html.LabelFor(model => model.Devices[deviceIndex ].Checked, Model.Devices[deviceIndex ].Name)
</div>
</div>
}

FluentValidation Improperly Validating Model from DropDown

I have the following two models (stripped to relevant parts):
Models\Department.cs:
public class DepartmentValidator : AbstractValidator<Department> {
public DepartmentValidator() {
RuleFor(d => d.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 256).WithMessage("The name cannot exceed 256 characters in length.");
}
}
[Validator(typeof(DepartmentValidator))]
public class Department {
public int Id { get; set; }
[Column(TypeName = "nvarchar")]
[MaxLength(256)]
public string Name { get; set; }
}
Models\FacultyMember.cs:
public class FacultyValidator : AbstractValidator<FacultyMember> {
public FacultyValidator() {
RuleFor(f => f.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 64).WithMessage("The name cannot exceed 64 characters in length.");
}
}
[Validator(typeof(FacultyValidator))]
public class FacultyMember {
public int Id { get; set; }
[Column(TypeName = "nvarchar")]
[MaxLength(64)]
public string Name { get; set; }
public virtual ICollection<Department> Departments { get; set; }
public FacultyMember() {
Departments = new HashSet<Department>();
}
}
I have the following controller code:
Controllers\FacultyController.cs:
// GET: Faculty/Create
public ActionResult Create() {
// Get Departments.
var departmentList = db.Departments.ToList().Select(department => new SelectListItem {
Value = department.Id.ToString(),
Text = department.Name
}).ToList();
ViewBag.DepartmentList = departmentList;
var facultyMember = new FacultyMember();
facultyMember.Departments.Add(new Department()); // Create a single dropdown for a department to start out.
return View(facultyMember);
}
// POST: Faculty/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Departments")] FacultyMember facultyMember) {
// Get Departments.
var departmentList = db.Departments.ToList().Select(department => new SelectListItem {
Value = department.Id.ToString(),
Text = department.Name
}).ToList();
ViewBag.DepartmentList = departmentList;
if (!ModelState.IsValid) { // Problem here...
return View(facultyMember);
}
db.Faculty.Add(facultyMember);
db.SaveChanges();
return RedirectToAction("Index");
}
Views\Faculty\Create.cshtml:
...
<div class="form-group">
#Html.LabelFor(model => model.Departments, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Departments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Departments, "", new { #class = "text-danger" })
</div>
</div>
...
Views\Shared\EditorTemplates\Department.cshtml:
#model MyProject.Models.Department
#Html.DropDownListFor(model => model.Id, ViewBag.DepartmentList as IEnumerable<SelectListItem>, "Select...", new { #class = "form-control" })
So, when I navigate to the create faculty page, everything displays properly; the 'Departments' field has a dropdown list with the departments in my database. However, upon submitting the form, my model state is invalid (see comment in code above). Upon further inspection, it seems that FluentValidation is spitting out an error because my "Name" field is null. That's exactly what it should do when I'm creating/editing departments, but for this dropdown in faculty members, it shouldn't be validating the entire department, should it? The only thing the dropdown is sending back is the Id, as I've specified.
The only thing that this dropdown sends is the Id of the department, which is properly received. So, what do I need to do to make this work? My goal is to have a dynamic set of dropdown lists, each populated with existing departments in the database. Similar to this example.
Please let me know if anything else needs explaining.
The solution, as explained by Stephen Muecke, was to create a view model to represent all data I wanted to pass to the form and back.
ViewModel\FacultyMemberViewModel.cs:
public class FacultyMemberViewModelValidator : AbstractValidator<FacultyMemberViewModel> {
public FacultyMemberViewModelValidator() {
RuleFor(f => f.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 64).WithMessage("The name cannot exceed 64 characters in length.");
RuleFor(s => s.SelectedDepartments)
.NotEmpty().WithMessage("You must specify at least one department.")
}
}
[Validator(typeof(FacultyMemberViewModelValidator))]
public class FacultyMemberViewModel {
public int Id { get; set; }
public string Name { get; set; }
public int[] SelectedDepartments { get; set; }
[DisplayName("Departments")]
public IEnumerable<SelectListItem> DepartmentList { get; set; }
}
Views\Faculty\Create.cshtml:
...
<div class="form-group">
#Html.LabelFor(model => model.DepartmentList, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.SelectedDepartments, Model.DepartmentList, new { #class = "form-control" }) #Html.ValidationMessageFor(model => model.SelectedDepartments, "", new { #class = "text-danger" })
</div>
</div>
...
Controllers\FacultyController.cs:
// GET: Faculty/Create
public ActionResult Create() {
var facultyMemberViewModel = new FacultyMemberViewModel {
DepartmentList = GetDepartmentList()
};
return View(facultyMemberViewModel);
}
// POST: Faculty/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,SelectedDepartments,DepartmentList")] FacultyMemberViewModel facultyMemberViewModel) {
if (!ModelState.IsValid) {
// Re-set the Department list.
if (facultyMemberViewModel.DepartmentList == null) {
facultyMemberViewModel.DepartmentList = GetDepartmentList();
}
return View(facultyMemberViewModel);
}
var facultyMember = new FacultyMember {
Id = facultyMemberViewModel.Id,
Name = facultyMemberViewModel.Name,
};
foreach (var departmentId in facultyMemberViewModel.SelectedDepartments) {
// I'm assuming this is safe to do (aka the records exist in the database)...
facultyMember.Departments.Add(db.Departments.Find(departmentId));
}
db.Faculty.Add(facultyMember);
db.SaveChanges();
return RedirectToAction("Index");
}

Two equal pieces of code work differently

Working on my project I faced a problem: EF didn't load the data from connected table, but in other practically equal piece of code all is perfect. Can somebody explain it?
Here is the situation:
All is good, type.name is loaded:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult pacientEdit([Bind(Include = "ID,comments")] Anamnesis anamnesis)
{
if (ModelState.IsValid)
{
db.Entry(anamnesis).State = EntityState.Modified;
db.SaveChanges();
return pacientDetails(anamnesis.ID);
}
return PartialView(anamnesis);
}
public ActionResult pacientDetails(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Anamnesis anamnesis = db.anamneses.Include(p => p.type).Where(p => p.ID == id).First();
if (anamnesis == null)
{
return HttpNotFound();
}
return PartialView("~/views/Anamnesis/pacientDetails.cshtml", anamnesis);
}
Here I run PacientEdit, then it flows to PacientDetails and gives me full anamnesis, including full type with it's name.
All is bad, type.name is null:
In the second case the name of type is null despite all my tries to load it from db.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult pacientEdit(Assigment assigment)
{
if (ModelState.IsValid)
{
db.Entry(assigment).State = EntityState.Modified;
db.SaveChanges();
return pacientDetails(assigment.ID);
//dirty fix, but works: return (new AssigmentsController()).pacientDetails(assigment.ID);
}
return PartialView(assigment);
}
public ActionResult pacientDetails(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Assigment assigment = db.assigments.Include(p => p.type).Where(p => p.ID == id).First();
if (assigment == null)
{
return HttpNotFound();
}
return PartialView("~/views/Assigments/pacientDetails.cshtml", assigment);
}
In this case all data in pacientDetails, including type_ID is loading, but type.name is null
Can somebody explain such behavior?
Used models:
public class Anamnesis
{
public int ID { get; set; }
public AnamnesisEventType type { get; set; }
public String comments { get; set; }
}
public class Assigment
{
public int ID { get; set; }
public AssigmentType type { get; set; }
public decimal? weight { get; set; }
public decimal? dose { get; set; }
public decimal? inADay { get; set; }
[DataType(DataType.MultilineText)]
public String comments { get; set; }
public String medicine { get; set; }
[DefaultValue(1)]
public int actual { get; set; }
public DateTime cancelDate { get; set; }
}
public class AnamnesisEventType
{
public int ID { get; set; }
public String name { get; set; }
}
public class AssigmentType
{
public int ID { get; set; }
public String name { get; set; }
public String description { get; set; }
}
Here is the view for pacientEdit for AssigmentsController:
#model WebApplication2.Models.Assigment
<form id="#String.Format("AssigmentsEdit{0}", Model.ID)">
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.type.ID)
#Html.HiddenFor(model => model.cancelDate)
#Html.HiddenFor(model => model.actual)
<div class="row">
<div class="col-md-4">
<strong>
#Html.DisplayFor(model => model.type.name)
</strong>
</div>
<div class="col-md-6">
<p>
#Html.EditorFor(model => model.medicine, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.medicine) } })
</p>
</div>
<div class="col-md-2">
<a onclick="CancelEdit('Assigments', #Model.ID);" class="btn btn-warning btn-sm"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></a>
<a class="btn btn-sm btn-primary" onclick="PostEditForm('Assigments', #Model.ID);">
<span class="glyphicon glyphicon-save" aria-hidden="true"></span>
</a>
</div>
</div>
<div class="row alert alert-info" style="margin-top:10px">
<div class="col-md-4">
<h4><span class="glyphicon glyphicon-tint" aria-hidden="true"></span> Назначение:</h4>
#Html.EditorFor(model => model.weight, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.weight) } })
#Html.EditorFor(model => model.dose, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.dose) } })
#Html.EditorFor(model => model.inADay, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.inADay) } })
</div>
<div class="col-md-8">
<h4><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> #Html.DisplayNameFor(model => model.comments):</h4>
<p>#Html.EditorFor(model => model.comments, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.comments) } })</p>
</div>
</div>
</form>
<hr />
I guess the problem is in the fact that you're trying to bind Assignment ID and AssignmentType ID using the same name.
Create an AssignmentViewModel class (which is anyway a good practice), where you'll have only the ID of member "type" like this:
public class AssigmentViewModel
{
public int ID { get; set; }
public int TypeId { get; set; }
public decimal? weight { get; set; }
public decimal? dose { get; set; }
public decimal? inADay { get; set; }
[DataType(DataType.MultilineText)]
public String comments { get; set; }
public String medicine { get; set; }
public int actual { get; set; }
public DateTime cancelDate { get; set; }
}
You could also try to modify the hiddenFor type like this:
#Html.HiddenFor(model => model.type)
Hope this help

Convention for adding items to a collection property from a select list in the create portion of CRUD?

My main entity is the Recipe which contains a collection of Ingredient items as follows:
public class Recipe {
[Key]
public virtual int RecipeId { get; set; }
public string RecipeName { get; set; }
...
public virtual ApplicationUser LastModifiedBy { get; set; }
public virtual IList<Ingredient> Ingredients { get; set; }
}
public class Ingredient {
public virtual int IngredientId { get; set; }
[Display(Name = "Name")]
public string IngredientName { get; set; }
....
public virtual IList<Recipe> Recipes { get; set; }
}
Which is fine. Then my controller and view for creating a new Recipe are as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "stuff to include")] Recipe recipe)
{
IList<int> ingredientIds = (ModelState.Values.ElementAt(1).Value.AttemptedValue).Split(',').Select(int.Parse).ToList(); //[1,2,3,4,5]
foreach (int id in ingredientIds) {
Ingredient ing = db.Ingredients.Where(i => i.IngredientId == id).FirstOrDefault() as Ingredient;
recipe.Ingredients.Add(ing);
}
db.Recipes.Add(recipe);
db.SaveChanges();
return RedirectToAction("Index");
ViewBag.Ingredients = new MultiSelectList(db.Ingredients,
"IngredientId", "IngredientName", string.Empty);
ViewBag.CreatedById = new SelectList(db.Users, "Id", "Email", recipe.CreatedById);
return View(recipe);
}
And the view:
#for (Int16 i = 0; i < 5; i++) {
<div class="form-group">
#Html.LabelFor(model => model.Ingredients, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Ingredients", null, htmlAttributes: new { #class = "form-control" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="Add Ingredients" class="btn btn-default" />
</div>
</div>
So this sets ModelState.Values.ElementAt(1).Value.AttemptedValue = "1,3,5,4,5" where this is a list of id numbers. I know I can come in before the if (ModelState.IsValid) and iterate through the above and place it into recipe.Ingredients which is fine except...
It feels just so un ASP.NET MVC like, as if there's no way they could have thought of so much and not thought of this scenario? Am I missing something here? The ingredients list will be too long to make a multi select list any use.
You are creating arbitrary dropdownlists that all have the same id (invalid html) and name attribute that has no relationship to your model and wont bind on post back. You first need to create view models that represent what you want to display.
public class RecipeVM
{
[Required]
public string Name { get; set; }
[Display(Name = Ingredient)]
[Required]
public List<int?> SelectedIngredients { get; set; }
public SelectList IngredientList { get; set; }
}
Then in the controller
public ActionResult Create()
{
RecipeVM model = new RecipeVM();
// add 5 'null' ingredients for binding
model.SelectedIngredients = new List<int?>() { null, null, null, null, null };
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(RecipeVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize new instance of your data model
// Map properties from view model to data model
// Add values for user, create date etc
// Save and redirect
}
private void ConfigureViewModel(RecipeVM model)
{
model.IngredientList = new SelectList(db.Ingredients, "IngredientId", "IngredientName");
}
View
#model RecipeVM
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
for (int i = 0; i < Model.SelectedIngredients.Count; i++)
{
#Html.LabelFor(m => m.SelectedIngredients[i])
#Html.DropDownListFor(m => m.SelectedIngredients[i], Model.IngredientList, "-Please select-")
}
}
Note this is based on your current implementation of creating 5 dropdowns to select 5 ingredients. In reality you will want to dynamically add ingredients (start with none). The answers here and here give you a few options to consider.

View not showing ValidationSummary and ValidationMessages

I have a View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="input-group">
<div class="input-group-addon">
#Html.Label("Employee number", new { #class = "control-label" })
</div>
<div class="a">
#Html.TextBoxFor(model => model.EmployeeNo, new {#class="form-control" })
#Html.ValidationMessageFor(model => model.EmployeeNo)
</div>
</div>
/*
* other fields
*/
}
and Controller:
[HttpPost]
public ActionResult Edit([Bind(Include="Id,EmployeeNo,Name,Surname,ContactInfo,RoleId")] User user)
{
ValidateRequestHeader(Request);
if (ModelState.IsValid)
{
unitOfWork.userRepository.Update(user);
unitOfWork.Save();
return Json(new { ok = true, newurl = Url.Action("Index") });
}
//ModelState.AddModelError("", "Niepoprawne dane");
ViewBag.RoleId = new SelectList(unitOfWork.roleRepository.Get(), "Id", "RoleName", user.RoleId);
return PartialView(user);
}
and model:
public partial class User
{
public User()
{
this.DeviceUsages = new HashSet<DeviceUsage>();
}
public int Id { get; set; }
[Required(ErrorMessage="Brak numeru pracownika")]
public string EmployeeNo { get; set; }
[Required(ErrorMessage = "Brak imienia")]
public string Name { get; set; }
[Required(ErrorMessage = "Brak nazwiska")]
public string Surname { get; set; }
[Required(ErrorMessage = "Brak Adresu email")]
public string ContactInfo { get; set; }
public int RoleId { get; set; }
}
Data annotations are working. If I leave eg. Name empty ModelState is not Valid in controler. But validation messages are not shown. If I uncomment this line:
ModelState.AddModelError("", "Niepoprawne dane"); this will be the only Model error shown in the View.
Where is a mistake in my code?
It's because you are using #Html.ValidationSummary(true) means excludePropertyErrors = true

Categories