Having some trouble understanding how to create and edit a collection of strings using a form. I've tried using EditorFor but it seems to have no luck and instead puts the following text into the form. I'm trying to edit the collection "Keywords".
System.Collections.Generic.HashSet`1[MVCModuleStarter.Models.Module]System.Collections.Generic.HashSet`1[MVCModuleStarter.Models.Module]
This is the Html I'm using the EditorFor in with a working EditorFor being used on a string for reference.
<div class="form-group">
#Html.LabelFor(model => model.Category, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Category, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Category, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Keywords, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Keywords, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Keywords, "", new { #class = "text-danger" })
</div>
</div>
This is the Edit method inside my controller;
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ModuleId,ModuleTitle,ModuleLeader,ModuleDescription,ImageURL,Category,Keywords")] Module module)
{
if (ModelState.IsValid)
{
int moduleId = module.ModuleId;
repository.UpdateModule(module);
repository.Save();
return RedirectToAction("Details", new { Id = moduleId });
}
return View(module);
}
This is the Model for reference;
[Required, StringLength(20), Display(Name = "Category")]
public string Category { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
Model for Keyword
public class Keyword
{
[Key, Display(Name = "ID")]
public int KeywordId { get; set; }
[Required, StringLength(100), Display(Name = "Keyword")]
public string KeywordTerm { get; set; }
public virtual ICollection<Module> Modules { get; set; }
}
}
Any help would be amazing, still new to this! Thanks!
You need to create an EditorTemplate for Keyword, for example
In /Views/Shared/EditorTemplates/Keyword.cshtml (add divs, class names etc as required)
#model Keyword
#Html.HiddenFor(m => m.KeywordId)
#Html.LabelFor(m => m.KeywordTerm)
#Html.TextBoxFor(m => m.KeywordTerm)
#Html.ValidationMessageFor(m => m.KeywordTerm)
Then in the main view
Html.EditorFor(m=> m.Keywords)
Note I have omitted the collection property Modules, but if you also want to edit them, the add another EditorTemplate for Modules
Alternatively you can use a for loop in the main view. This would mean the collection need to be IList<T>
for(int i = 0; i < Model.Keywords.Count, i++)
{
#Html.HiddenFor(m => m.Keywords[i].KeywordId)
// other properties of Keyword
for (int j = 0; j < Model.Keywords[i].Modules.Count; j++)
{
#Html.TextBoxFor(m => m.Keywords[i].Modules[j].SomeProperty)
// other properties of Module
}
}
Related
I have the following model:
public class FormDimensions
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "formID")]
public String FormID { get; set; }
[JsonProperty(PropertyName = "width")]
public float Width { get; set; }
[JsonProperty(PropertyName = "height")]
public float Height { get; set; }
[JsonProperty(PropertyName = "padding")]
public float Padding { get; set; }
[JsonProperty(PropertyName = "inspectedFields")]
public Dictionary<String, Dictionary<String,FieldDimension>> InspectedFields { get; set; }
}
The field "InspectedFields" is the field in question..
public class FieldDimension
{
[JsonProperty(PropertyName = "name")]
public String Name { get; set; }
[JsonProperty(PropertyName = "page")]
public int Page { get; set; }
[JsonProperty(PropertyName = "dimension")]
public String Dimension { get; set; }
}
I have been using the following code in my Edit Razor view for the model:
foreach (var entry in Model.InspectedFields)
{
<h3>#entry.Key</h3>
<div class="card-block">
#foreach (var page in entry.Value.Values)
{
<div class="form-group">
#Html.Label("Value Name", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => page.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessage(page.Name, new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Page Dimensions", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => page.Dimension, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessage(page.Dimension, new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Page Number", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => page.Page, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessage(page.Page.ToString(), new { #class = "text-danger" })
</div>
</div>
}
</div>
<hr />
}
So when I go to save my model, it actually nulls out the entire InspectedFeilds dictionary and cause my razor view to crash. However, it will save all the other values in the model. I am not sure if this is because my implementation or I am missing something about handling dictionaries in a razor view..
In order to post a dictionary you must both post the Key and Value. You also need to use a for loop and index the dictionary. For example:
#for (var i = 0; i < Model.InspectedFields.Count; i++)
{
#Html.HiddenFor(m => m.InspectedFields[i].Key)
for (var j = 0; j < Model.InspectedFields[i].Value.Count; j++)
{
#Html.HiddenFor(m => m.InspectedFields[i].Value[j].Key)
#Html.EditorFor(m => m.InspectedFields[i].Value[j].Value.Page)
}
}
HTML has been stripped from sample code to illustrate the format better.
This question already has answers here:
MVC5 - How to set "selectedValue" in DropDownListFor Html helper
(5 answers)
Closed 5 years ago.
I'm trying to populate the edit view with data which has dropdownlists using ViewModel . The data populates but the Dropdownlists are not selected as per the data.
Have checked similar issues in SO like [SO1][1] , SO2 , SO3 but not able to resolve . Know it might be something silly I'm missing but unable to find.
Code:
Viewmodel:
public class ProductVM
{
public int ID { get; set; }
[Required]
[DisplayName("Product Name")]
public string ProductName { get; set; }
public int SupplierID { get; set; }
public IEnumerable<SelectListItem> Suppliers { get; set; }
public Supplier Supplier { get; set; }
public int UnitID { get; set; }
public IEnumerable<SelectListItem> Units { get; set; }
public Unit Unit { get; set; }
public int ItemCategoryID { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
[DisplayName("Categories")]
public ItemCategory ItemCategory { get; set; }
}
Controller Edit :
public ActionResult Edit(int id)
{
var productVM = GetProductById(id);
return View(productVM);
}
private ProductVM GetProductById(int id)
{
Product product = db.Products.Find(id);
var productVM = new ProductVM();
var suppliers = new SelectList(db.Suppliers, "ID", "SupplierName", product.SupplierID);
productVM.Suppliers = suppliers.ToList();
var categories = new SelectList(db.ItemCategories, "ID", "Name", product.ItemCategoryID);
productVM.Categories = categories.ToList();
var units = new SelectList(db.Units, "ID", "Name", product.UnitID);
productVM.Units = units.ToList();
}
View :
<div class="form-group">
#Html.LabelFor(model => model.ProductName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProductName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductName, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.SupplierID, Model.Suppliers, "--Select--")
#Html.ValidationMessageFor(model => model.SupplierID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitID, "UnitID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.UnitID, (IEnumerable<SelectListItem>)Model.Units, "--Select--")
#Html.ValidationMessageFor(model => model.UnitID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ItemCategoryID, "ItemCategoryID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.ItemCategoryID, (IEnumerable<SelectListItem>)Model.Categories, "--Select--")
#Html.ValidationMessageFor(model => model.ItemCategoryID, "", new { #class = "text-danger" })
</div>
</div>
Appreciate any help. Thanks in advance.
[1]: Binding Viewmodel Property to dropdown selected item in razor
to-dropdown-selected-item-in-razor
Whatever you set in the selectedValue parameter in the SelectList is going to be ignored. Why? Because you have a strongly typed view and you are binding SupplierID property of ProductVM to the dropdown. In your GetProductById method, you are not actually populating the productVM.SupplierID, UnitID and ItemCategoryID. So they will have the default int value, 0.
So you can change you method to this:
private ProductVM GetProductById(int id)
{
Product product = db.Products.Find(id);
var productVM = new ProductVM
{
// No need to pass the 4th parameter "selectedValue" in SelectList.
Suppliers = new SelectList(db.Suppliers, "ID", "SupplierName"),
Categories = new SelectList(db.ItemCategories, "ID", "Name"),
Units = new SelectList(db.Units, "ID", "Name"),
// Populate these properties
SupplierID = product.SupplierID,
ItemCategoryID = product.ItemCategoryID,
UnitID = product.UnitID
};
return productVM ;
}
The optional selectedValue parameter is usually used when we're not using strongly typed views. If you were to change your View to the following, then your dropdown would have pre selected the value:
#Html.DropDownList("Supplier", Model.Suppliers, "--Select--")
You would want to set an initial value into your selected values in the controller before passing the model to the view if you want the dropdownlists to load with a pre-selected value. For example:
public ActionResult Edit(int id)
{
var productVM = GetProductById(id);
//Set your pre-determined values here and they will show up on the view once binded
productVM.SupplierID = 1;
productVM.UnitID = 1;
productVM.ItemCategoryID = 1;
return View(productVM);
}
This question does a great job of breaking that process down: How to get DropDownList SelectedValue in Controller in MVC
I am currently working on a project to model a bikestore. In my 'Order' object, I have a lis object for the Bike items on the order. How would I add bikes to this list? I.E I want to display a list of availiable bikes in the Create view, an add one or more of them to the order.
My Controller:
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "OrderNumber,CustomerName,OrderDate,PickupDate,TotalCost,PaymentMethod")] Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
My Inventory model
public class Inventory
{
public int Id { get; set; }
public string SerialNumber { get; set; }
public virtual Store Store { get; set; }
public int? StoreId { get; set; }
public string Model { get; set; }
public string Description { get; set; }
public Decimal InventoryCost { get; set; }
public Decimal RecSalePrice { get; set; }
public Decimal SalePrice { get; set; }
public string PaymentMethod { get; set; }
public virtual BikeCategory Category { get; set; }
public int? CategoryId { get; set; }
}
My Order model:
namespace BikeStore.Models
{
public class Order
{
public Order()
{
OrderedItems = new List<Inventory>();
}
public string CustomerName { get; set; } //FROM CONTROLLER User.Identity.Name
public virtual List<Inventory> OrderedItems { get; set; }
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int OrderNumber { get; set; }
In the create view for orders:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Order</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomerName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomerName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OrderDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OrderDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OrderDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PickupDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PickupDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PickupDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalCost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalCost, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalCost, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PaymentMethod, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PaymentMethod, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PaymentMethod, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Start by creating view models to represent what you want to display/edit in the view (add display and validation attributes as appropriate)
public class InventoryVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class OrderVM
{
public string PaymentMethod { get; set; }
public List<InventoryVM> Inventory { get; set; }
}
Note that CustomerName, OrderDate and Total are not appropriate (you don't want a user editing these - they should be set in the POST method immediately before saving the order). Not sure what PickupDate represents but if its the actual date then that's not appropriate either (it would be set separately when the order is collected). I would also suggest that PaymentMethod be an enum or collection of PaymentType's and that you and use a dropdownlist in the view for selection.
Then the GET method would be
public ActionResult Create()
{
// Get all available bikes, for example
var inventory = db.Inventory;
OrderVM model = new OrderVM
{
Inventory = inventory.Select(i => new
{
ID = i.ID,
Name = i.Model // modify this to suit what you want to display in the view
}).ToList()
};
return View(model);
}
And in the view
#model yourAssembly.OrderVM
#using (Html.BeginForm())
{
for(int i = 0; i < Model.Inventory.Count; i++)
{
#Html.HiddenFor(m => m.Inventory[i].ID)
#Html.CheckBoxFor(m => m.Inventory[i].IsSelected)
#Html.LabelFor(m => m.Inventory[i].IsSelected, Model.Inventory[i].Name)
}
#Html.TextBoxFor(m => m.PayentMethod)
<input type="submit" value="Create" />
}
And the POST method would be
public ActionResult Create(OrderVM model)
{
// Initialize a new Order and map properties from view model
var order = new Order
{
CustomerName = User.Identity.Name,
OrderDate = DateTime.Now,
....
PaymentMethod = model.PaymentMethod
}
// Save the order so that you now have its `ID`
IEnumerable<int> selectedItems = model.Inventory.Where(i => i.IsSelected).Select(i => i.ID);
foreach(var item in selectedItems)
{
// You have not shown the model for this so just guessing
var orderItem = new OrderItem{ OrderID = order.Id, InventoryId = item };
db.OrderItems.Add(orderItem);
}
db.SaveChanges();
}
Side notes:
If you want to be able to allow users to select more that one of any
item, the you could change bool IsSelected to say int Quantity
If you you want to display additional information about the items,
say Description and Cost you can include additional properties
in the InventoryVM view model and display them with
#Html.DisplayFor(m => m.Inventory[i].Description)
If you want to display a total cost for all selected items in the
view, you will need to use javascript/jquery
If ModelState could be invalid, you will need to repopulate the
properties of InventoryVM before you return the view (as shown
only the ID and IsSelected properties post back) or include
hidden inputs for the other properties in the view
I am currently making a website in ASP.NET MVC where i would like to use EditorTemplates to display a List inside my ViewModel. RowViewModel has a List of RowDataViewModels. I'm trying to make a Create View for RowViewModel, where i can use
#Html.EditorFor(model => model.RowData)
and therefore i need an EditorTemplate. I created and EditorTemplate under:
Views/Shared/EditorTemplates/RowDataViewModel.cshtml
And i also tried putting the EditorTemplates folder under the /Home/ view folder, but nothing seems to work. No breakpoints are being hit inside the editortemplate. I think i did it this way before, but i might be forgetting something.
Any ideas?
RowViewModel:
public class RowViewModel
{
[Required]
[Display(Name="Name")]
public string Name { get; set; }
public List<RowDataViewModel> RowData { get; set; }
}
RowDataViewModel:
public class RowDataViewModel
{
public string Name { get; set; }
public string Value { get; set; }
public string DataType { get; set; }
}
EditorTemplate - RowDataViewModel.cshtml:
#using iDealWebRole.ViewModels
#model RowDataViewModel
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<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.Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Value, "", new { #class = "text-danger" })
</div>
</div>
The problem is here: #Html.EditorFor(model => model.RowData)
You should use your editor for every row in RowData, like:
#for (int i = 0; i < Model.RowData.Count(); i++)
{
#Html.EditorFor(model => Model.RowData[i])
}
Hey guys i need your help, i have a create employee form in MVC. I fill the form and select from the dropdown list and when i click submit, every other field returns a value except for the dropdown. i can't seem to fix the problem. this is what the form looks like.
this is the CreateEmployee Model
public class CreateEmployee
{
public int Id { get; set; }
public string FullName { get; set; }
[Required]
public string Notes { get; set; }
public int Department { get; set; }
public IEnumerable<AngTestDepartment> Departments { get; set; }
public bool PerkCar { get; set; }
public bool PerkStock { get; set; }
public bool PerkSixWeeks { get; set; }
public string PayrollType { get; set; }
}
the action controller
public ActionResult Employee(){
using (var db = new DepartmentDbContext())
{
var model = new CreateEmployee();
model.Departments = db.AngTestDepartments.ToList();
return View(model);
} }
and the view
#using (Html.BeginForm("CreateEmployee", "Employee", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Employee</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.FullName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FullName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FullName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Notes, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Notes, "", new { #class = "text-danger" })
</div>
</div>
// the drop down doesnt return a value on submit
<div class="form-group">
#Html.LabelFor(model => model.Department, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.Departments,
Model.Departments.Select(d => new SelectListItem()
{
Value = d.id.ToString(),
Text = d.Department
}
), new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Department, "", new { #class = "text-danger" })
</div>
</div> }
and the action result called on submit
public ActionResult CreateEmployee(CreateEmployee newEmployee)
{
DbEmployeeTable.DbEmployeeTable_EmployeeTable(newEmployee);
return RedirectToAction("Employees");
}
thank you very much for your help.
Change the dropdownlist code to:
#Html.DropDownListFor(m => m.Department, new SelectList(Model.Departments.Select(d => new SelectListItem()
{
Value = d.id.ToString(),
Text = d.Department
}
)), new { htmlAttributes = new { #class = "form-control" } })
Dropdownlistfor's first argument should be the value that you're trying to get, and having the second be a select list will save you persistence-related headaches later.
As an aside, I'd also make the Model.Departments.Select result a model property so you don't clutter your view so much, but that's just a style concern.
Instead of
#Html.DropDownListFor(m => m.Departments,
try this...
#Html.DropDownListFor(m => m.Department,
That should return the department Id as an int from the selected value.