send Dynamic list to Controller from View - asp.net-mvc

I am getting quite responses when I add/delete rows.
When page is loaded the default rows 3 will be added.(But user can delete those rows)
When I am trying to add new row then 4 value rows are coming in controller but when I am trying to delete any default row then it is not showing the newly added row.
Here is my below code.
#model ProductRegistration.Models.InstalledProductInformationModel
#using (Html.BeginForm())
{
#for (int i = 0; i < ((Model != null && Model.listInstallProducts != null) ? Model.listInstallProducts.Count : 3); i++)
{
<tr>
<td>
#Html.TextBoxFor(m => m.listInstallProducts[i].SerialNumber, new { #class = "form-control input-sm input-Serial", #placeholder = "Enter serial number", #maxlength = 50, #OnKeypress = "return alphanumeric_only(event);" })
#Html.ValidationMessageFor(m => m.listInstallProducts[i].SerialNumber)
<div class="SerialErrorDisplay" style="color: red"></div>
</td>
<td>
#Html.DropDownListFor(m => m.listInstallProducts[i].ModelNumber, new SelectList(string.Empty, "Value", "Text"), "--Select Model--", new { #class = "form-control input-sm input-Model" })
#Html.ValidationMessageFor(m => m.listInstallProducts[i].ModelNumber)
<div class="ModelErrorDisplay" style="color: red"></div>
</td>
<td>
#Html.TextBoxFor(m => m.listInstallProducts[i].InstallationDate, new { #class = "form-control input-sm input-Date", #maxlength = 50, #placeholder = "DD/MM/YYYY" })
#Html.ValidationMessageFor(m => m.listInstallProducts[i].InstallationDate)
<div class="DateErrorDisplay" style="color: red"></div>
</td>
<td>
<img src="~/Images/delete_icon.png" onclick="DeleteProduct(this);" />
</td>
</tr>
}
<input class="btn btn-primary top-padding" type="submit" value="Previous" id="back-step" name="direction"/>
}
In Controller:
public ActionResult InstalledProductInfo(InstalledProductInformationModel InstalledProducts, string direction)
{
if(ModelState.IsValid)
{
return RedirectToAction("EquipmentOwnerInfo");
}
return View(InstalledProducts);
}
Model is:
public class InstalledProductInformationModel : InstalledProductInformation
{
public InstalledProductInformation installedProductInformation { get; set; }
public List<InstalledProductInformation> listInstallProducts { get; set; }
}
please help me out.

By default, the DefaultModelBinder required collection indexers to start at zero and be consecutive. If you delete the first item (indexer = 0) then no items will be bound. If you delete the 3rd item (indexer = 2) then only the first 2 items will be bound. You can override this behavior by adding an input for a special property named Index where the value equals the indexer.
#for (int i = 0; i < ((Model != null && Model.listInstallProducts != null) ? Model.listInstallProducts.Count : 3); i++)
{
<tr>
<td>
#Html.TextBoxFor(m => m.listInstallProducts[i].SerialNumber, new { #class = "form-control input-sm input-Serial", #placeholder = "Enter serial number", #maxlength = 50, #OnKeypress = "return alphanumeric_only(event);" })
#Html.ValidationMessageFor(m => m.listInstallProducts[i].SerialNumber)
<div class="SerialErrorDisplay" style="color: red"></div>
</td>
....
<td>
// Add the following input
<input type="hidden" name="listInstallProducts.Index" value="#i" />
<img src="~/Images/delete_icon.png" onclick="DeleteProduct(this);" />
</td>
</tr>
}
<input class="btn btn-primary top-padding" type="submit" value="Previous" id="back-step" name="direction"/>
Side note: You should be adding the 3 items in the controller before you pass the model to the view so you get proper model binding and then simply use #for (int i = 0; i < Model.listInstallProducts.Count; i++)

List indexes are enumerated continuously, so there can be no gap. If You remove one row, the list index is no more continuous. The new row should be there if You will deal with indexes.

Related

ASP.NET MVC - add to a viewmodel list and submit

I have an ASP.NET MVC program with an order/odc request form. I have a customer, order and order item model. What I want to do is allow the user to place an order for a list of items to be approved. I have the viewmodel being passed to the form/view with a few fields including a list of order item objects. I am able to dynamically add rows to the table which shows the list of order items but on submit there is nothing in the viewmodel list. What am I doing wrong? How do I pass the items entered into the table to the view so that I can submit to the database?
Controller
public ActionResult NewOdc()
{
var viewModel = new NewOdcViewModel()
{
OdcItems = new List<tblOdcItem>()
};
viewModel.OdcItems.Add(new tblOdcItem());
return View(viewModel);
}
I call this code from jQuery to add a new item to the list:
public ActionResult GetView(string rowCount)
{
tblOdcItem item = new tblOdcItem();
return PartialView("_OdcItemEditor", item);
}
And on submit I call this code:
[HttpPost]
public ActionResult NewOdcSubmit(NewOdcViewModel viewModel)
{
_context.tblOdcs.Add(new tblOdc());
...
I'm using a foreach to go through the list and create a partial for each item.
View:
#using (Html.BeginForm("NewOdcSubmit", "Odc", FormMethod.Post))
{
if (Model != null)
{
#Html.HiddenFor(m => m.OdcItems);
}
<div class="panel panel-info">
<div class="panel-heading">
<h2 class="panel-title">Enter New ODC</h2>
</div>
<div class="panel-body">
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.User.UserName, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="col-md-2 col-sm-3">
#Html.TextBoxFor(model => model.User.UserName, new { #Value = ((PM_Portal2020.Models.tblUser)Session["User"]).UserName, #readonly = true })
</div>
#Html.LabelFor(model => model.User.Phone, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="col-md-2 col-sm-3">
#Html.TextBoxFor(model => model.User.Phone, new { #Value = ((PM_Portal2020.Models.tblUser)Session["User"]).Phone })
</div>
</div>
<div class="form-group col-md-10 col-sm-12">
<label>Expenses</label>
<table id="submissionTable" class="table table-bordered">
<thead>
<tr>
<th>Qty</th>
<th>Description</th>
<th>Estimated Cost</th>
</tr>
</thead>
<tbody>
#foreach (PM_Portal2020.Models.tblOdcItem item in Model.OdcItems)
{
#Html.Partial("_OdcItemEditor", item)
}
</tbody>
</table>
<p>
<button id="add" type="button" class="btn btn-primary">Add</button>
</p>
</div>
<div class="form-group col-lg-10 col-sm-12">
#Html.LabelFor(model => model.Details, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="">
#Html.TextAreaFor(model => model.Details, new { #class = "form-control" })
</div>
</div>
</div>
<div class="panel-footer">
<div class="">
<button type="submit" class="btn btn-success">Save</button>
#Html.ActionLink("Back", "Index")
</div>
</div>
</div>
}
PartialView in Shared folder:
#model PM_Portal2020.Models.tblOdcItem
<tr #Html.Id("tablerow" + Model.ID)>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.Quantity, new { #class = "text-box single-line", name = "Quantity[" + Model.ID + "]", type = "text", value = "", required = "required" })
</div>
</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.Description, new { #class = "text-box single-line", name = "Description[" + Model.ID + "]", type = "text", value = "", required = "required", id = "itemDesc" })
</div>
</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.EstimatedCost, new { #class = "text-box single-line", name = "EstimatedCost[" + Model.ID + "]", type = "text", value = "", required = "required" })
</div>
</td>
<td>
<button type="button" class="btn btn-primary" onclick="removeTr(this);">
<span class="glyphicon glyphicon-trash"></span>
</button>
</td>
</tr>
View Model
public class NewOdcViewModel
{
public NewOdcViewModel()
{
}
public IList<tblOdcItem> OdcItems { get; set; }
public string Details { get; set; }
public int OdcId { get; set; }
public tblUser User { get; set; }
}
It submits to the controller but the odcitems list is always count = 0. Any help would be great. Thanks
Here is the javascript example, just use this function on add/delete operation to re-arrange name.
function RearangeName(){
var i = 0;
$("#submissionTable>tbody>tr").each(function () {
$(this).find("input").each(function () {
if ($(this).prop("name").indexOf('Quantity') > 0) {
$(this).attr('name', "OdcItems[" + i + "].Quantity");
}
if ($(this).prop("name").indexOf('Description') > 0) {
$(this).attr('name', "OdcItems[" + i + "].Description");
}
if ($(this).prop("name").indexOf('EstimatedCost') > 0) {
$(this).attr('name', "OdcItems[" + i + "].EstimatedCost");
}
});
i++;
});
}
the name should be matched with model property, so in partial view, you have set name as OdcItems[0].Quantity instead of Quantity[" + Model.ID + "].
#Html.TextBoxFor(model => model.Quantity, new { #class = "text-box single-line", name = "OdcItems[0].Quantity", type = "text", value = "", required = "required" })
eg.
OdcItems[0].Quantity
OdcItems[1].Quantity
OdcItems[2].Quantity
....
OdcItems[n].Quantity

C# MVC form doesn't work with input form attribute

I need to use form inside table cell with fields in another cells. I'm using input form attribute. MVC passing data to controller works correctly but validation before form send doesn't work with this attribute.
<tr>
<td>
#Html.EditorFor(model => item.Code, new { htmlAttributes = new { #class = "form-control inline-edit", form = "editForm" + item.Id } })
#Html.ValidationMessageFor(model => item.Code)
</td>
<td>
#Html.EditorFor(model => item.Name, new { htmlAttributes = new { #class = "form-control inline-edit", form = "editForm" + item.Id } })
#Html.ValidationMessageFor(model => item.Name)
</td>
<td class="text-right">
#using (Html.BeginForm("UpdatePrintMaterial", "Production", FormMethod.Post, new { id = "editForm" + item.Id }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => item.Id)
<button class="btn btn-default save" data-toggle="tooltip" title="Zapisz"><i class="fa fa-save"></i></button>
}
</td></tr>
How to workaroud this issue to use validation before sending data?

MVC PagedList Dynamic Page/Page Size determined by model

I am using the PagedList.mvc helper to handle the paging of my application, in all examples i have found people tend to explicitly set the page size. I wish to set the page size dynamically from data in a model as well create new pages from the data as well.
Table
I have the table stipulated in the image above, i would like it that page 1 is all entries that have the MonthInt as 1 and page 2 all entries as MonthInt 2 etc.. however i want each page size to be determined by how many entries there are for each month.. it currently dynamically sets the sizes of the pages according to the first page and only limits the first page to display entries with the MonthInt 1-3, after that it goes to the next page but doesnt follow the same rule.. Any help/suggestions would be great,
Controller Code:
// GET: Customers/Details/5
public ActionResult Details(int? id, string sortOrder, string searchString, string currentFilter, int? page)
{
Customer customer = db.Customers.Find(id);
var viewModel = new CustomerViewModel();
if (id != null)
{
IEnumerable<Vehicle> vehicles = db.Vehicles.Where(c => c.CustomerId == customer.Id);
List<Vehicle> vehiclesList = vehicles.OrderBy(m => m.MonthId).ThenBy(r => r.Register).ToList();
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewBag.CurrentFilter = searchString;
int pageSize = vehicles.Count(m => m.MonthId <= 3);
int pageNumber = (page ?? 1);
if (!String.IsNullOrEmpty(searchString))
{
vehiclesList = vehicles.Where(r => r.Register.Contains(searchString.ToUpper())
|| r.License.Contains(searchString.ToUpper())).ToList();
}
PagedList<Vehicle> pagedList = new PagedList<Vehicle>(vehiclesList, pageNumber,pageSize);
viewModel = new CustomerViewModel()
{
Vehicles = pagedList,
Customer = customer
};
}
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (customer == null)
{
return HttpNotFound();
}
return View(viewModel);
}
View Code:
#model Application.ViewModels.CustomerViewModel
#using PagedList;
#using PagedList.Mvc
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Fleet Details";
}
<div>
<h2>#Model.Customer.Name</h2>
#using (Html.BeginForm("Details", "Customers", FormMethod.Get))
{
<div class="input-group">
<div class="input-group-btn">
<div class="btn-toolbar">
#Html.TextBox("searchString", ViewBag.CurrentFilter as string, null, new {#class = "form-control", placeholder = "Find by Register/License", autofocus = "autofocus"})
<input class="btn-warning btn" type="submit" value="search"/>
</div>
</div>
</div>
}
<hr />
#if (!Model.Vehicles.Any())
{
<p>No Vehicles loaded</p>
<br />
}
else
{
<table class="table table-hover table-bordered">
<tr>
<th>#Html.DisplayName("Owner")</th>
<th>#Html.DisplayName("License")</th>
<th>#Html.DisplayName("Make")</th>
<th>#Html.DisplayName("Register")</th>
<th>#Html.DisplayName("MonthInt")</th>
<th>#Html.DisplayName("Month")</th>
</tr>
#foreach (var item in Model.Vehicles)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.OwnerName)</td>
<td>#Html.DisplayFor(modelItem => item.License)</td>
<td>#Html.DisplayFor(modelItem => item.Make)</td>
<td>#Html.DisplayFor(modelItem => item.Register)</td>
<td>#Html.DisplayFor(modelItem => item.MonthId)</td>
<td>#Html.DisplayFor(modelItem => item.MonthName)</td>
<td>
<div class="btn-toolbar">
#Html.ActionLink("Edit", "Edit", "Vehicles", new { id = item.VehicleId }, new { #class = "btn btn-primary" })
#Html.ActionLink("Details", "Details", "Vehicles", new { id = item.VehicleId }, new { #class = "btn btn-default" })
#Html.ActionLink("Delete", "Delete", "Vehicles", new { id = item.VehicleId }, new { #class = "btn btn-danger" })
</div>
</td>
</tr>
}
</table>
}
<div class="btn-toolbar">
#Html.ActionLink("Add Vehicle", "Create", "Vehicles", new { id = Model.Customer.Id }, new { #class = "btn-success btn" })
#Html.ActionLink("Edit Customer Name", "Edit", new { id = Model.Customer.Id }, new { #class = "btn btn-primary" })
#Html.ActionLink("Delete Customer", "Delete", new { id = Model.Customer.Id }, new { #class = "btn-danger btn" })
#Html.ActionLink("Back to List", "Index", null, new { #class = "btn-default btn" })
</div>
<br/>
Page #(Model.Vehicles.PageCount < Model.Vehicles.PageNumber ? 0 : Model.Vehicles.PageNumber) of #Model.Vehicles.PageCount
#Html.PagedListPager(Model.Vehicles, page => Url.Action("Details",
new { page, pageSize = #Model.Vehicles.PageSize }))
Showing #Model.Vehicles.FirstItemOnPage to #Model.Vehicles.LastItemOnPage
of #Model.Vehicles.TotalItemCount Vehicles
Example: Example 1
Example 2
Solution kind of just happened while trying other things. Current solution allows for each month to be on its own page. It might not be the best solution but it works.
Added custom class to group data:
public class Group<T, TK>
{
public TK Key;
public IEnumerable<T> Values;
}
Updated ViewModel:
public PagedList<Group<Vehicle, string>> VehiclesGroup { get; set; }
Updated Details Controller:
public ActionResult Details(int? id, string searchString, string currentFilter, int? page, int? item)
{
var customer = db.Customers.Find(id);
if (customer == null)
{
return HttpNotFound();
}
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var vehicles = from f in db.Vehicles
where f.CustomerId == customer.Id
select f;
var vehiclesList = vehicles.OrderBy(m => m.MonthId).ThenBy(r => r.Register).ToList();
var groupedVehicles = from v in vehiclesList
group v by v.MonthName
into g
select new Group<Vehicle, string> { Key = g.Key, Values = g };
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewBag.CurrentFilter = searchString;
var pageSize = vehicles.Count(v => v.MonthId <= 0);
if (pageSize == 0)
{
pageSize = 1;
}
var pageNumber = (page ?? 1);
if (!string.IsNullOrEmpty(searchString))
{
vehicles = vehicles.Where(r => r.Register.Contains(searchString.ToUpper())
|| r.License.Contains(searchString.ToUpper())
|| r.Make.Contains(searchString.ToUpper())
|| r.OwnerName.Contains(searchString.ToUpper())
|| r.Vin.Contains(searchString.ToUpper())
|| r.MonthName.Contains(searchString.ToUpper()));
vehiclesList = vehicles.ToList();
groupedVehicles = from v in vehiclesList
group v by v.MonthName
into g
select new Group<Vehicle, string> { Key = g.Key, Values = g };
}
var pagedList = new PagedList<Group<Vehicle, string>>(groupedVehicles, pageNumber, pageSize);
var viewModel = new CustomerVehicleViewModel()
{
VehiclesGroup = pagedList,
Customer = customer
};
return View(viewModel);
}
Updated View:
#model Application.ViewModels.CustomerVehicleViewModel
#using PagedList.Mvc
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Fleet Details";
if (TempData["Error"] != null)
{
ViewBag.Error = TempData["Error"];
}
}
<h2>#Model.Customer.Name</h2>
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-6" style="margin-bottom: 1%">
#using (Html.BeginForm("Details", "Customers", FormMethod.Get))
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="input-group">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>
#Html.TextBox("searchString", ViewBag.CurrentFilter as string, null, new { #class = "form-control", placeholder = "Search", autofocus = "autofocus", style = "margin-right:1%" })
</div>
<div class="input-group-btn">
<input class="btn-warning btn" type="submit" value="search" />
#Html.ActionLink("Reset", null, null, null, new { #class = "btn btn-info", style = "margin-left: 5%" })
</div>
</div>
}
</div>
<div class="col-md-6">
<div class="btn-toolbar">
#Html.ActionLink("Add Vehicle", "Create", "Vehicles", new { id = Model.Customer.Id }, new { #class = "btn-success btn" })
#Html.ActionLink("Edit Customer", "Edit", new { id = Model.Customer.Id }, new { #class = "btn btn-primary" })
#Html.ActionLink("Delete Customer", "Delete", new { id = Model.Customer.Id }, new { #class = "btn-danger btn" })
#Html.ActionLink("Back to List", "Index", null, new { #class = "btn-default btn" })
#Html.ActionLink("BRN/ID", "ViewFile", "Customers", new { cid = Model.Customer.Id, id = Model.Customer.FileId }, new { #class = "btn-info btn", target = "_blank" })
</div>
</div>
</div>
</div>
</div>
#Html.ValidationMessage("", $"{ViewBag.Error}", new { #class = "text-danger" })
<hr />
#if (!Model.VehiclesGroup.Any())
{
<p>No Vehicles loaded</p>
<br />
}
else
{
<table class="table table-hover table-bordered" id="table">
<thead>
<tr>
<th>#Html.DisplayName("Owner")</th>
<th>#Html.DisplayName("License")</th>
<th>#Html.DisplayName("Make")</th>
<th>#Html.DisplayName("Register")</th>
<th>#Html.DisplayName("VIN")</th>
<th>#Html.DisplayName("Month")</th>
<th class="remove">
<button id="btnPrint" class="btn btn-warning"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Print Table</button>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.VehiclesGroup)
{
foreach (var i in item.Values)
{
<tr>
<td>#Html.DisplayFor(modelItem => i.OwnerName)</td>
<td>#Html.DisplayFor(modelItem => i.License)</td>
<td>#Html.DisplayFor(modelItem => i.Make)</td>
<td>#Html.DisplayFor(modelItem => i.Register)</td>
<td>#Html.DisplayFor(modelItem => i.Vin)</td>
<td>#Html.DisplayFor(modelItem => i.MonthName)</td>
<td class="remove">
<div class="btn-toolbar">
#Html.ActionLink("Edit", "Edit", "Vehicles", new { id = i.VehicleId }, new { #class = "btn btn-primary" })
#Html.ActionLink("Details", "Details", "Vehicles", new { id = i.VehicleId }, new { #class = "btn btn-default" })
#Html.ActionLink("Delete", "Delete", "Vehicles", new { id = i.VehicleId }, new { #class = "btn btn-danger" })
#Html.ActionLink("Scan", "ViewFile", "Vehicles", new { cId = i.CustomerId, id = i.VehicleId }, new { #class = "btn btn-info", target = "_blank" })
</div>
</td>
</tr>
}
<tr>
<td colspan="3">#item.Key</td>
<td colspan="3"></td>
</tr>
}
</tbody>
</table>
}
<br />
Page #(Model.VehiclesGroup.PageCount < Model.VehiclesGroup.PageNumber ? 0 : Model.VehiclesGroup.PageNumber) of #Model.VehiclesGroup.PageCount
#Html.PagedListPager(Model.VehiclesGroup, page => Url.Action("Details",
new
{
page,
currentFilter = ViewBag.CurrentFilter,
pageSize = #Model.VehiclesGroup.PageSize
}))
Total of #Model.VehiclesGroup.TotalItemCount Pages
Updated Display of month January/page 1
Page 1
Updated Display of month February/page 2
Page 2
Updated Display of Page 3
Page 3
Hope it helps someone, or if anyone has better ideas please share!

Incorrect Item gets deleted in MVC

After I delete a list item ex:2nd item out of 4 from the below code by clicking delete button on the row, it shows 1,2,3 in view instead of 1,3,4. When I debug the view it shows right value, however on page rendering TextBoxFor AeroDromeID is showing wrong. Also the delete aerodrome button shows the right value. Can some one help me to fix this.
Controller
[HttpPost]
[MultipleButton(Name = "action", Argument = "DeleteAerodrome")]
public ActionResult DeleteAerodrome(FormCollection fc)
{
int AerodromeID = Int32.Parse(fc["action:DeleteAerodrome"]);
var studentSignoutModel = new StudentSignoutViewModel();
UpdateModel<StudentSignoutViewModel>(studentSignoutModel);
if (studentSignoutModel.Aerodromes.Count > 0)
studentSignoutModel.Aerodromes.RemoveAll(m => m.AerodromeID == AerodromeID);
return View("StudentSignoutStageView", studentSignoutModel);
}
View
#if (Model.Aerodromes != null && Model.Aerodromes.Any())
{
for (int i = 0; i < Model.Aerodromes.Count; i++)
{
<tr>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].AerodromeID, new { #class = "form-control"})</td>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].CloudBase, new { #class = "form-control" })</td>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].Visibility, new { #class = "form-control" })</td>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].Wind, new { #class = "form-control" })</td>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].Crosswind, new { #class = "form-control" })</td>
<td>#Html.TextBoxFor(a => Model.Aerodromes[i].InterTempo, new { #class = "form-control" })</td>
<td>
<input type="submit" id="BtnDelAerodrome" value="#Model.Aerodromes[i].AerodromeID" name="action:DeleteAerodrome" style="font-size:1em" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete the record with AerodromeID = '+ #Model.Aerodromes[i].AerodromeID);" />
</td>
</tr>
}
}
I think there should be a bug with Data Binding concept while rendering from server side, when we use TextBoxFor.
When I use html tag input with the model binder,it worked.
This is easily replicable.

ASP.NET View IEnumerable Item validation

I have this ASP:NET MVC Razor View which has a IEnumerable as model .
I'm creating a table where each line represents a item from the IEnumerable.
I'm using this code:
#foreach (var item in Model)
{
<tr>
<td>
<input type="checkbox" name="selectedFoo" value="#item.isAdded"
#(Html.Raw(item.isAdded? "checked=\"checked\"" : "")) />
#item.FooName
</td>
<td>
#Html.EditorFor(modelItem=> item.Name, new { htmlAttributes = new { #class = "form-control", style = "width: 70px" } })
#Html.ValidationMessageFor(modelItem=> item.Name, "", new { #class = "text-danger" })
</td>
</tr>
}
My problem is that when I enter an incorrect value for the "Name" property all the other text input get the validation error.
Solutions?
Thank you.
I would remove '#:' from the table elements. Also using the razor syntax I would generate a form tag. That may have something to do with it. Also add the ValidationSummary method.
Is that snippet you posted the complete view?
#using (Html.BeginForm())
{
#Html.ValidationSummary(true, "", new class{#class="text-danger"})
#foreach (var item in Model)
{
<tr>
<td>
<input type="checkbox" name="selectedFoo" value="#item.isAdded"
#(Html.Raw(item.isAdded? "checked=\"checked\"" : "")) />
#item.FooName
</td>
<td>
#Html.EditorFor(modelItem=> item.Name, new { htmlAttributes = new { #class = "form-control", style = "width: 70px" } })
#Html.ValidationMessageFor(modelItem=> item.Name, "", new { #class = "text-danger" })
</td>
</tr>
}
}
If it's a partial view make sure the property 'Name' doesn't exist on any other fields in the view. 'Name' is very generic, perhaps you should rename it something more descriptive. 'FullName', 'ProductName'. ect....
Your foreach loop is generating duplicated name attributes for each textbox, and the ValidationMessageFor() applies to all elements with that name. In addition, you will never be able to bind to you model when you submit your form.
You need to use a for loop or custom EditorTemplate to generate your elements (refer this answer for more detail)
Assuming your model is IList<T>, then
#for(int i = 0; i < Model.Count; i++)
{
#Html.EditorFor(m => m[i].Name, new { htmlAttributes = new { #class = "form-control", style = "width: 70px" } })
#Html.ValidationMessageFor(m => m[i].Name, "", new { #class = "text-danger" })
}
In addition, use the CheckBoxFor() method to generate checkboxes (assumes selectedFoo is typeof bool)
#Html.CheckBoxFor(m => m[i].selectedFoo)
#Html.LabelFor(m => m[i].selectedFoo, Model[i].FooName)
This can be solved by creating another instance of the model solely for validation purpose in the view.
#{YourNameSpace.Models.Model ValidationModel = new YourNameSpace.Models.Model ();}
Than you can use it in the form like this:
<div class="form-group">
<input asp-for="#ValidationModel.PropertyName" name="[0].Name" class="form-control" />
<span asp-validation-for="#ValidationModel.PropertyName" class="text-danger"></span>
</div>
Or using HTML helper:
#Html.ValidationMessageFor(modelItem => ValidationModel.Name, "", new { #class = "text-danger" })

Resources