Get Submitted Data From Dropdown in View Model? - c#

I have a people controller for user management and I'm trying to figure out how to get the dropdown choice when the user submits from the edit page. Whenever I hit submit on the page, none of the values from the view model seem to carry through to the post. I can't get the value they chose from the drop down to set the role.
See view model below:
public class PersonViewModel
{
public int PersonId { get; set; }
[Display(Name = "Full Name")]
public string FullName { get; set; }
public string Email { get; set; }
[Display(Name = "Current Role")]
public string SetRole { get; set; }
public List<RoleListViewModel> Roles { get; set; }
}
See controller edit functions below:
// GET: People/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.people.Find(id);
if (person == null)
{
return HttpNotFound();
}
PersonViewModel pvm = new PersonViewModel();
List<IdentityRole> roles = adb.Roles.ToList();
var rlvm = new List<RoleListViewModel>();
roles.ForEach(x => rlvm.Add(new RoleListViewModel { RoleId = x.Id, RoleName = x.Name }));
pvm.PersonId = person.PersonId;
pvm.FullName = person.FirstName + " " + person.LastName;
pvm.Email = person.Email;
pvm.Roles = rlvm;
ViewBag.RoleList = new SelectList(rlvm, "RoleName", "RoleName", person.CurrentRole);
return View(pvm);
}
// POST: People/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(PersonViewModel pvm)
{
if (ModelState.IsValid)
{
db.Entry(pvm).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
var usr = new AccountController();
var pers = db.people.Where(x => x.PersonId == pvm.PersonId).FirstOrDefault();
usr.UserManager.AddToRoleAsync(pers.NetId, /* their choice should go here but how? */);
db.SaveChanges();
return View(pvm);
}
Here is the cshtml:
<div class="form-group">
#Html.LabelFor(model => model.Roles, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="form-control-static">
#Html.DropDownList("RoleList", null, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Roles, "", new { #class = "text-danger" })
</div>
</div>

Make a variable in your View Model to store the selected value and then in the view use
#Html.DropDownListFor(m => m.SelectedRoleVariable, RolesSelectList, new { #class = "form-control" });

Related

MVC Parent Child Modal File Upload problem

I have a parent child scenario for getting header detail data. here is my code attached, the problem is when i use a file upload it does not work
Person Model:
public class Person
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Display(Name = "First Name")]
[Required]
[StringLength(255, MinimumLength = 3)]
public string Name { get; set; }
[Display(Name = "Last Name")]
[Required]
[StringLength(255, MinimumLength = 3)]
public string Surname { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Error: Must Choose a Country")]
[Range(1, int.MaxValue, ErrorMessage = "Select a Country")]
public int CountryID { get; set; }
public virtual Country Country { get; set; }
}
Address Model:
public class Address
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Error: Must Choose a City")]
[Range(1, int.MaxValue, ErrorMessage = "Select a City")]
public int CityID { get; set; }
public virtual City City { get; set; }
[Required]
[Display(Name = "Street Address")]
public string Street { get; set; }
[Required]
[DataType(DataType.ImageUrl)]
public string ImageUrl { get; set; }
[NotMapped]
[DataType(DataType.Upload)]
public HttpPostedFileBase ImageUpload { get; set; }
[Required]
[Phone]
public string Phone { get; set; }
public int PersonID { get; set; }
public virtual Person Person { get; set; }
}
PeopleController:
private DataDb db = new DataDb();
// GET: People
public async Task<ActionResult> Index()
{
return View(await db.People.ToListAsync());
}
// GET: People/Details/5
public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = await db.People.Where(p => p.Id == id).Include(p => p.Addresses).SingleAsync();
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
// GET: People/Edit/5
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var person = await db.People
.Include(p => p.Addresses)
.Where(p => p.Id == id)
.SingleAsync();
if (person == null)
{
return HttpNotFound();
}
ViewBag.CountryID = new SelectList(db.Country, "CountryID", "CountryName", person.CountryID);
return View(person);
}
// POST: People/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit( Person person)
{
if (ModelState.IsValid)
{
db.Entry(person).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewBag.CountryID = new SelectList(db.Country, "CountryID", "CountryName", person.CountryID);
return View(person);
}
// GET: People/Delete/5
public async Task<ActionResult> Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = await db.People.FindAsync(id);
if (person == null)
{
return HttpNotFound();
}
return View(person);
}
// POST: People/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
Person person = await db.People.FindAsync(id);
db.People.Remove(person);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
Address Controller:
private DataDb db = new DataDb();
public ActionResult Index(int id)
{
ViewBag.PersonID = id;
var addresses = db.Addresses.Where(a => a.PersonID == id).OrderBy(a => a.City.CityName);
return PartialView("_Index", addresses.ToList());
}
[ChildActionOnly]
public ActionResult List(int id)
{
ViewBag.PersonID = id;
var addresses = db.Addresses.Where(a => a.PersonID == id);
return PartialView("_List", addresses.ToList());
}
public ActionResult Create(int PersonID)
{
//Address address = new Address();
//address.PersonID = PersonID;
//FillCity(address);
Address adv = new Address();
adv.PersonID = PersonID;
FillCityModel(adv);
return PartialView("_Create", adv);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Address address, HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
int addressID = clsStatic.newAddressID();
address.Id = addressID;
var uploadDir = "~/images";
var imagePath = Path.Combine(Server.MapPath(uploadDir), address.ImageUpload.FileName);
var imageUrl = Path.Combine(uploadDir, address.ImageUpload.FileName);
address.ImageUpload.SaveAs(imagePath);
address.ImageUrl = imageUrl;
db.Addresses.Add(address);
db.SaveChanges();
string url = Url.Action("Index", "Addresses", new { id = address.PersonID });
return Json(new { success = true, url = url });
}
FillCity(address);
return PartialView("_Create", address);
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Address address = db.Addresses.Find(id);
if (address == null)
{
return HttpNotFound();
}
//ViewBag.CityID = new SelectList(db.City, "CityID", "CityName", address.CityID);
FillCity(address);
return PartialView("_Edit", address);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Address address )
{
var uploadDir = "~/images";
if (address.ImageUpload != null)
{
var imagePath = Path.Combine(Server.MapPath(uploadDir), address.ImageUpload.FileName);
var imageUrl = Path.Combine(uploadDir, address.ImageUpload.FileName);
address.ImageUpload.SaveAs(imagePath);
address.ImageUrl = imageUrl;
}
if (ModelState.IsValid)
{
db.Entry(address).State = EntityState.Modified;
db.SaveChanges();
string url = Url.Action("Index", "Addresses", new { id = address.PersonID });
return Json(new { success = true, url = url });
}
//var file = Request.Files[0];
//if (file != null && file.ContentLength > 0)
//{
// var fileName = address.Id + Path.GetExtension(file.FileName);
// var path = Path.Combine(Server.MapPath("~/Images/Addresses/"), fileName);
// file.SaveAs(path);
//}
FillCity(address);
//ViewBag.CityID = new SelectList(db.City, "CityID", "CityName", address.CityID);
return PartialView("_Edit", address);
}
private void FillCity(Address address)
{
List<SelectListItem> city = new List<SelectListItem>();
foreach (City item in db.City)
{
if (item.CityID != address.CityID)
city.Add(new SelectListItem { Text = item.CityName, Value = item.CityID.ToString() });
else
city.Add(new SelectListItem { Text = item.CityName, Value = item.CityID.ToString(), Selected = true });
}
ViewBag.CityID = city;
}
private void FillCityModel(Address address)
{
List<SelectListItem> city = new List<SelectListItem>();
foreach (City item in db.City)
{
if (item.CityID != address.CityID)
city.Add(new SelectListItem { Text = item.CityName, Value = item.CityID.ToString() });
else
city.Add(new SelectListItem { Text = item.CityName, Value = item.CityID.ToString(), Selected = true });
}
ViewBag.CityID = city;
}
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Address address = db.Addresses.Find(id);
if (address == null)
{
return HttpNotFound();
}
return PartialView("_Delete", address);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Address address = db.Addresses.Find(id);
db.Addresses.Remove(address);
db.SaveChanges();
string url = Url.Action("Index", "Addresses", new { id = address.PersonID });
return Json(new { success = true, url = url });
}
Here in _Edit View of Address I want to have File Upload Field
#model TestAjax.Models.Address
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Edit Address</h4>
</div>
<div class="modal-body">
#using (Html.BeginForm("Edit", "Addresses", FormMethod.Post,
new { id = "editForm", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.PersonID)
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.CityID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CityID, (List<System.Web.Mvc.SelectListItem>)ViewBag.CityID, new { #class = "form-control selectpicker" })
#Html.ValidationMessageFor(model => model.CityID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Street, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Street, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Street, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Phone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Phone, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Phone, "", new { #class = "text-danger" })
</div>
</div>
<div class="editor-field">
#Html.LabelFor(m => m.ImageUpload)
#Html.TextBoxFor(m => m.ImageUpload, new { type = "file" })
</div>
</div>
<button class="btn" type="button" data-dismiss="modal">Cancel</button>
<input class="btn btn-primary" type="submit" value="Save" />
}
</div>
<div class="modal-footer">
Footer
</div>
<script>
$(document).ready(function () {
refill();
});
function refill() {
var id = #Model.CityID;
$('#CityID').val(id);
}
</script>
But when i press the save button in address controller edit action i don't have the file selected.
please help.
Download Source Code

MVC DropDownListFor null reference [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 4 years ago.
This model is used to define a view:
namespace OnlineStore.ViewModels
{
public class SubCategoryVM
{
[Key]
public int ID { get; set; }
[Required]
public virtual string Name { get; set; }
[Required(ErrorMessage = "Parent Category Name is required")]
public virtual string ParentName { get; set; }
public IEnumerable<SelectListItem> categoryNames { get; set; }
}
}
Inside controller:
public ActionResult createSubCategory()
{
SubCategoryVM model = new SubCategoryVM();
var cNames = db.Categories.ToList();
model.categoryNames = cNames.Select(x
=> new SelectListItem
{
Value = x.Name,
Text = x.Name
});
return View(model);
}
[HttpPost]
public ActionResult createSubCategory(int? id, SubCategoryVM model)
{
SubCategory sc = new SubCategory();
if (ModelState.IsValid)
{
sc.ParentName = model.ParentName;
sc.Name = model.Name;
}
return View();
}
and View:
#model OnlineStore.ViewModels.SubCategoryVM
<div class="form-group">
#Html.LabelFor(model => model.ParentName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.ParentName, Model.categoryNames, "--Please select an option--", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ParentName, "", new { #class = "text-danger" })
</div>
This code is throwing a null-reference exception on line #Html.DropDownListFor(model => model.ParentName, Model.categoryNames, "--Please select an option--", new { #class = "form-control" }) saying:
Model.categoryName (Object reference not set to an instance of an object).
Please help me debug it.
Thanks in advance.
Problem is when you are posting the form and returning the View with the form data in case of invalid form, categoryNames in the model is becoming null and you have to repopulate the categoryNames before returning the view with model again.
So update your createSubCategory post method as follows:
[HttpPost]
public ActionResult createSubCategory(int? id, SubCategoryVM model)
{
SubCategory sc = new SubCategory();
if (ModelState.IsValid)
{
sc.ParentName = model.ParentName;
sc.Name = model.Name;
}
var cNames = db.Categories.ToList();
model.categoryNames = cNames.Select(x
=> new SelectListItem
{
Value = x.Name,
Text = x.Name
});
return View(model);
}

C# MVC Dropdownlist not updating Model

I am having problem understanding as to why my model is not updating when I select a new value from my dropdownlist control?
Here is my model
public class UserViewModel
{
public Users users { get; set; }
public IEnumerable<SelectListItem> UserRoles { get; set; }
}
Controller
//GET
public ActionResult Edit(int id)
{
var vm = new UserViewModel();
vm.users = repository.GetById(id);
vm.UserRoles = db.UserRoles.Select(
x => new SelectListItem
{
Selected = true,
Text = x.UserRoleName,
Value = x.UserRoleID.ToString()
}
);
if (vm == null)
{
return HttpNotFound();
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel model)
{
if(ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
And finally my View
<div class="form-group">
<label class="control-label col-md-2">User Role</label>
<div class="col-md-10">
#Html.HiddenFor(model => model.users.UserRoleID)
#Html.DropDownListFor(model => model.UserRoles, (IList<SelectListItem>)ViewBag.UserRoles, "-- Select One --", new { #class = "form-control" })
</div>
</div>
I have stepped through the code and in the Collection can see UserRoles in the collection but I am not sure if I am passing the value correctly?
UPDATE
I have updated my POST method for updating the model
public ActionResult Edit(int id, UserViewModel model)
{
var user = repository.GetById(id);
if (ModelState.IsValid)
{
if (user != null)
{
user.Username = model.users.Username;
user.Forename = model.users.Forename;
user.Lastname = model.users.Lastname;
user.Email = model.users.Email;
user.Status = model.users.Status;
user.UserRoleID = Convert.ToInt32(model.UserRoles);
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View();
}
}
return View();
}
However on Submit it is giving me a Null reference exception on the dropdownlist as shown below? Now sure why?
(IList<SelectListItem>)ViewBag.UserRoles
you data exist in vm.UserRoles lists not in ViewBag.UserRoles but you are attaching list using Viewbag
ViewBag.UserRoles = db.UserRoles.Select(
x => new SelectListItem
{
Selected = true,
Text = x.UserRoleName,
Value = x.UserRoleID.ToString()
}
assign the list to the view model then you will get access to the list from the page
Finally solved the issue. Created a new property inside my ViewModel which will store the selected value for When posting back to Controller
public class UserViewModel
{
public Users users { get; set; }
public IEnumerable<SelectListItem> UserRoles { get; set; }
public string selectedRole { get; set; }
}
Made change to my View to include the new property
#Html.DropDownListFor(model => model.selectedRole, Model.UserRoles, "-- Select One --", new { #class = "form-control" })
and on Post I pass the selected value
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, UserViewModel model)
{
var user = repository.GetById(id);
if (ModelState.IsValid)
{
if (user != null)
{
user.Username = model.users.Username;
user.Forename = model.users.Forename;
user.Lastname = model.users.Lastname;
user.Email = model.users.Email;
user.Status = model.users.Status;
user.UserRoleID = Convert.ToInt32(model.selectedRole);
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View();
}
return View();
}
I think the wrong code is in the View:
<div class="col-md-10">
#Html.HiddenFor(model => model.users.UserRoleID)
#Html.DropDownListFor(model => model.UserRoles (IList<SelectListItem>)ViewBag.UserRoles, "-- Select One --", new { #class = "form-control" })
</div>
The next code should work:
<div class="col-md-10">
#Html.DropDownListFor(model => model.users.UserRolesID, new SelectList(Model.UserRoles, "Id", "Name"), new { #class = "form-control" })
</div>
where there is a direct binding between model.users.UserRolesID and the list containing user roles (I suppose that UserRoles is done as id,description)

asp.net mvc 5 dropdownlist not showing item in list just after adding it

What I'm trying to do...
I am trying to get a newly added item to display in a cascading dropdownlist.
Overview...
The first dropdownlist (I'll call it ddlCategory) is for selecting a Category of electrical devices (ie. Appliances, Audio-Visual, Lighting, etc.). The second dropdownlist (I'll call ddlElecDev) is populated with the devices which are filtered by the selected Category. If the device isn't listed in ddlElecDev then the user can click a link to add a new one. After saving the newly added electrical device, the user is redirected back to the original page with the electrical device id as a parameter.
All the above seems to work fine. However, when the user is redirected to the first page, not only is the newly added electrical device not selected in ddlElecDev, but it doesn't even appear in the list. Strangely, if I refresh the page, it is automatically selected.
Can anyone explain to me how to get the newly added device to be selected without having to refresh the page?
Here's the markup for selecting the category and electrical device:
<div class="form-group">
#Html.LabelFor(model => model.SelectedCategory, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedCategory, Model.Categories, "Select a Category", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedCategory, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ElectricalDeviceID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.CascadingDropDownListFor(
expression: model => model.ElectricalDeviceID,
triggeredByProperty: model => model.SelectedCategory,
url: Url.Action("GetElectricalDevices", "ElectricalDeviceConfigurations"),
ajaxActionParamName: "categoryId",
optionLabel: "Select an Electrical Device",
disabledWhenParrentNotSelected: false,
htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ElectricalDeviceID, "", new { #class = "text-danger" })
#Html.ActionLink("Not Listed? Add a new Electrical Device", "AddNew", "ElectricalDevices", new { returnUrl = string.Format("/ElectricalDeviceConfigurations/AddConfiguration?eventVendorId={0}", Model.EventVendorID) }, null)
</div>
</div>
The documentation for the Cascading DropDownList Helper can be found at
https://github.com/alexanderar/Mvc.CascadeDropDown
Here's the Controller method for selecting the Electrical Device:
public ActionResult AddConfiguration(int? eventVendorId, int? newElecDev)
{
(Some code removed for brevity)
var selectedCategoryId = db.ElectricalDeviceCategoryLookups.Where(cat => cat.Category == Enums.ElectricalDeviceCategory.All).FirstOrDefault().ID;
var electricalDeviceID = (newElecDev.HasValue) ? newElecDev : null;
return View(new ElecDevConfigSelectionViewModel { EventVendorID = eventVendorId, EventVendor = eventVendor, Categories = GetCategories(), SelectedCategory = selectedCategoryId, ElectricalDeviceID = electricalDeviceID });
}
Here's the controller methods for populating the Categories and the Electrical Devices:
private List<SelectListItem> GetCategories()
{
var categories = new List<SelectListItem>();
db.ElectricalDeviceCategoryLookups.OrderBy(c => c.Description).ToList().ForEach(item => categories.Add(new SelectListItem { Text = item.Description, Value = item.ID.ToString() } ));
return categories;
}
public ActionResult GetElectricalDevices(int? categoryId)
{
if (categoryId.HasValue)
{
var selCategory = db.ElectricalDeviceCategoryLookups.Where(cat => cat.ID == categoryId).FirstOrDefault().Category;
var elecDevicesSelectList = new List<SelectListItem>();
var elecDevices = (selCategory == Enums.ElectricalDeviceCategory.All) ? db.ElectricalDevices.OrderBy(ed => ed.Name).ToList() : db.ElectricalDevices.Where(ed => ed.Category == selCategory).OrderBy(ed => ed.Name).ToList();
elecDevices.ForEach(ed => elecDevicesSelectList.Add(new SelectListItem { Text = ed.Name, Value = ed.ID.ToString() }));
return Json(elecDevicesSelectList, JsonRequestBehavior.AllowGet);
}
return null;
}
Here's the View Model:
public class ElecDevConfigSelectionViewModel
{
public int? EventVendorID { get; set; }
public EventVendor EventVendor { get; set; }
[Display(Name = "Category")]
public int SelectedCategory { get; set; }
public IList<SelectListItem> Categories { get; set; }
[Display(Name = "Electrical Device")]
public int? ElectricalDeviceID { get; set; }
}
Here's the controller methods for adding a new electrical device:
// GET: ElectricalDevices/AddNew
public ActionResult AddNew(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
var selectedCategoryId = db.ElectricalDeviceCategoryLookups.FirstOrDefault().ID;
return View(new AddNewElectricalDeviceViewModel { ReturnUrl = returnUrl, Categories = GetCategories(), SelectedCategory = null });
}
// POST: ElectricalDevices/AddNew
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddNew(AddNewElectricalDeviceViewModel model)
{
if (ModelState.IsValid)
{
var selectedCategory = db.ElectricalDeviceCategoryLookups.Find(model.SelectedCategory);
var electricalDevice = new ElectricalDevice
{
Name = model.ElectricalDevice.Name,
Description = model.ElectricalDevice.Description,
Category = selectedCategory.Category,
Wattage = model.ElectricalDevice.Wattage
};
db.ElectricalDevices.Add(electricalDevice);
db.SaveChanges();
var returnUrl = string.Format("{0}&newElecDev={1}", model.ReturnUrl, electricalDevice.ID);
return RedirectToLocal(returnUrl);
}
return View();
}
Here's the View Model for adding a new electrical device:
public class AddNewElectricalDeviceViewModel
{
public string ReturnUrl { get; set; }
[Display(Name = "Category")]
public int? SelectedCategory { get; set; }
public IList<SelectListItem> Categories { get; set; }
public ElectricalDevice ElectricalDevice { get; set; }
}

Combine multiple properties in #html.Editorfor Razor

We have to combine multiple properties into one EditorFor field in a Razor view.
We have the properties Quantity, UnitOfMeasure and Ingredient. These need to be combined so the user can just type what he or she needs i.e. 10 kg potatoes, instead of entering the information into multiple fields.
Once this is done, we also need autocomplete on the UOM and ingredient properties.
I have created a partial view for this code.
#model IEnumerable<RecipeApplication.Models.RecipeLine>
<div class="form-group">
#Html.Label("Ingrediënten", htmlAttributes: new { #class = "control-label col-md-2" })
<div>
#foreach (var item in Model)
{
<p>
#Html.EditorFor(modelItem => item.Quantity, new { htmlAttributes = new { #class = "form-control-inline" } })
#Html.EditorFor(modelItem => item.UnitOfMeasure.Abbreviation, new { htmlAttributes = new { #class = "form-control-inline" } })
#Html.EditorFor(modelItem => item.Ingredient.Name, new { htmlAttributes = new { #class = "form-control-inline" } })
</p>
}
</div>
</div>
Obviously this is not the intention.
And this is the code for the Edit functions:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
RecipeModel recipeModel = db.Recipes.Find(id);
if (recipeModel == null)
{
return HttpNotFound();
}
GetRecipeLines(id);
return View(recipeModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Name,Description,ImageUrl")] RecipeModel recipeModel, int?id)
{
if (ModelState.IsValid)
{
db.Entry(recipeModel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
GetRecipeLines(id);
return View(recipeModel);
}
I have looked on Google and StackOverflow but I can't find a proper answer to get this done.
Personally I wouldn't even know where to start at this moment.
I hope someone can help figuring this out.
Thanks.
Add a new getter property on ReceipLine
C# 6.0 Syntax:
public string QuantityUomIngredient =>
$"{Quantity} {UnitOfMeasure?.Abbreviation ?? ""} {Ingredient?.Name ?? ""}";
Then your view should look like this
#Html.EditorFor(modelItem => item.QuantityUomIngredient ...
And then build a custom model binder to parse QuantityUomIngredient into its corresponding properties (this part should be fun to implement). But be sure to do a good validation on the input so you have good data to parse.
Thanks for the anwer Leo Nix, it surely put me into the right direction.
Here is the code I wrote so far and it seems to work like a charm. (I did not include error handling yet.)
public class RecipeLine
{
[Key]
public int RecipeLineId { get; set; }
public int RecipeId { get; set; }
public double Quantity { get; set; }
public virtual UnitOfMeasureModel UnitOfMeasure { get; set; }
public virtual IngredientModel Ingredient { get; set; }
public string QuantityUomIngredient => $"{Quantity} {UnitOfMeasure?.Abbreviation ?? ""} {Ingredient?.Name ?? ""}";
}
And the custom Binder I wrote. This one took quite some extra research.
class RecipeLineCustomBinder : DefaultModelBinder
{
private RecipeApplicationDb db = new RecipeApplicationDb();
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// Get the QuantityCustomIngredient from the webform.
string quantityUomIngredient = request.Form.Get("QuantityUomIngredient");
// Get the IngredientID from the webform.
int recipeID = int.Parse(request.Form.Get("RecipeId"));
// Split the QuantityCustomIngredient into seperate strings.
string[] quantityUomIngredientArray = quantityUomIngredient.Split();
//string[] quantityUomIngredientArray = quantityUomIngredient.Split(new string[] { " " }, 2, StringSplitOptions.RemoveEmptyEntries);
if (quantityUomIngredientArray.Length >= 3)
{
// Get the quantity value
double quantityValue;
bool quantity = double.TryParse(quantityUomIngredientArray[0], out quantityValue);
// Get the UOM value.
string uom = quantityUomIngredientArray[1];
UnitOfMeasureModel unitOfMeasure = null;
bool checkUOM = (from x in db.UnitOfMeasures
where x.Abbreviation == uom
select x).Count() > 0;
if (checkUOM)
{
unitOfMeasure = (from x in db.UnitOfMeasures
where x.Abbreviation == uom
select x).FirstOrDefault();
}
// Get the ingredient out of the array.
string ingredient = "";
for (int i = 2; i < quantityUomIngredientArray.Length; i++)
{
ingredient += quantityUomIngredientArray[i];
if (i != quantityUomIngredientArray.Length - 1)
{
ingredient += " ";
}
}
bool checkIngredient = (from x in db.Ingredients where x.Name == ingredient select x).Count() > 0;
IngredientModel Ingredient = null;
if (checkIngredient)
{
Ingredient = (from x in db.Ingredients
where x.Name == ingredient
select x).FirstOrDefault();
}
// Return the values.
return new RecipeLine
{
Quantity = quantityValue,
UnitOfMeasure = unitOfMeasure,
Ingredient = Ingredient,
RecipeId = recipeID
};
}
else
{
return null;
}
}
}
In the Razor view this is the code that I used:
<div class="form-group">
#Html.LabelFor(model => model.QuantityUomIngredient, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuantityUomIngredient, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuantityUomIngredient, "", new { #class = "text-danger" })
</div>
</div>
I added the custom binder in Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelBinders.Binders.Add(typeof(RecipeLine), new RecipeLineCustomBinder());
}
}
And finally added the custom binder to the controller
[HttpPost]
public ActionResult Create([ModelBinder(typeof(RecipeLineCustomBinder))] RecipeLine recipeLine)
{
if (ModelState.IsValid)
{
db.RecipeLines.Add(recipeLine);
db.SaveChanges();
return RedirectToAction("Index", new { id = recipeLine.RecipeId });
}
return View(recipeLine);
}
I hope this will help other developers as well.

Categories

Resources