Updates by partialview not affected to model - c#

I have a problem with updating ViewModel by PartialView. PartialView includes Devices along with the location that varies depending on the DropDownList selected by the Customer. (At the bottom I present sample screenshot). The problem is that after accepting the submit button the property Devices in the ViewModel(FiscalizationViewModel) is not updating. Here are examples of models. I'm not sure I'm trying to solve this problem properly.
namespace TestMVC.ViewModels
{
public class FiscalizationViewModel
{
public int CustomerId { get; set; }
public string FiscalizationDate { get; set; }
public List<DevicesToFiscalizationViewModel> Devices { get; set; }
public FiscalizationViewModel()
{
Devices = new List<DevicesToFiscalizationViewModel>();
}
public IEnumerable<DevicesToLocalization> GetSelectedIds()
{
return (from d in Devices where d.Selected select new DevicesToLocalization() { DeviceId = d.DeviceId, LocalizationId = d.LocalizationId }).ToList();
}
}
public class DevicesToFiscalizationViewModel
{
public int DeviceId { get; set; }
public string DeviceName { get; set; }
public bool Selected { get; set; }
public string SerialNumber { get; set; }
public int LocalizationId { get; set; }
public IEnumerable<Localization> Localizations { get; set; }
public DevicesToFiscalizationViewModel()
{
Localizations = new List<Localization>();
}
}
}
Here is the method that is called by the Customer DropDownList event
public PartialViewResult CustomerChanged(int CustomerId)
{
var localizations = db.Localizations.Where(i => i.CustomerId == CustomerId).ToList();
var devicesToFsc = (from d in db.Devices
select new DevicesToFiscalizationViewModel()
{
DeviceId = d.DeviceId,
DeviceName = d.Name,
SerialNumber = d.SerialNumber,
}).ToList();
foreach (var item in devicesToFsc)
{
item.Localizations = localizations;
}
return PartialView("~/Views/Fiscalizations/EditorTemplates/DevicesToFiscalizationViewModel.cshtml", devicesToFsc);
//--------------------------------
$("#customerChanged").on("change", function () {
$.ajax(
{
url: '/Fiscalizations/CustomerChanged?CustomerId=' + $(this).val(),
type: 'GET',
data: "",
contentType: 'application/json; charset=utf-8',
success: function (data) {
$("#devicesToFiscalization").html(data);
}
});
});
This is little partial of Views (Fiscalization create view)
#model TestMVC.ViewModels.FiscalizationViewModel
<table class="table" id="devicesToFiscalization">
<thead>
...
</thead>
#Html.Partial("~/Views/Fiscalizations/EditorTemplates/DevicesToFiscalizationViewModel.cshtml", Model.Devices)
</table>
PartialView:
#model IEnumerable<TestMVC.ViewModels.DevicesToFiscalizationViewModel>
#foreach(var item in Model)
{
<tbody>
<tr>
<td style="text-align:center">
<div class="checkbox">
#Html.EditorFor(m => item.Selected)
</div>
</td>
<td>
#Html.DisplayFor(m => item.DeviceName)
</td>
<td>
#Html.DisplayFor(m => item.SerialNumber)
</td>
<td>
#Html.DropDownList("LocalizationId", new SelectList(item.Localizations, "LocalizationId", "Name"), "Select", htmlAttributes: new { #class = "form-control", style = "width: 200px;" })
</td>
<td>
#Html.HiddenFor(m => item.DeviceId)
</td>
</tr>
</tbody>
Here is a screenshot of how it looks
click
and here is screenshot from debugger with bad result
bad

Based on my understating from your views, your issues is using different models for main view and partial view. You should use exactly the same model in both the model binding could update the model on server side. keep both models TestMVC.ViewModels.FiscalizationViewModel or IEnumerable<TestMVC.ViewModels.DevicesToFiscalizationViewModel>

It works :) Below I show how it looks after changes
main view:
#model TestMVC.ViewModels.FiscalizationViewModel
<div id="devicesToFiscalization">
#Html.Partial("~/Views/Fiscalizations/EditorTemplates/DevicesToFiscalizationViewModel.cshtml", Model)
</div>
Partial view:
#model TestMVC.ViewModels.FiscalizationViewModel
<table class="table">
<thead>
<tr>
<th>
Select
</th>
<th>
Name
</th>
<th>
Serial number
</th>
<th>
Localization
</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Devices.Count; i++)
{
<tr>
<td style="text-align:center">
<div class="checkbox">
#Html.EditorFor(m => m.Devices[i].Selected)
</div>
</td>
<td>
#Html.DisplayFor(m => m.Devices[i].DeviceName)
</td>
<td>
#Html.DisplayFor(m => m.Devices[i].SerialNumber)
</td>
<td>
#Html.DropDownListFor(m => m.Devices[i].LocalizationId, new SelectList(Model.Devices[i].Localizations, "LocalizationId", "Name"), "Select", htmlAttributes: new { #class = "form-control", style = "width: 200px;" })
</td>
<td>
#Html.HiddenFor(m => m.Devices[i].DeviceId)
</td>
</tr>
}
</tbody>
and CustomerChanged method:
public PartialViewResult CustomerChanged(int CustomerId)
{
var localizations = db.Localizations.Where(i => i.CustomerId == CustomerId).ToList();
var Devices = (from d in db.Devices
select new DevicesToFiscalizationViewModel()
{
DeviceId = d.DeviceId,
DeviceName = d.Name,
SerialNumber = d.SerialNumber,
}).ToList();
foreach (var item in Devices)
{
item.Localizations = localizations;
}
var fsc = new FiscalizationViewModel();
fsc.Devices = Devices;
return PartialView("~/Views/Fiscalizations/EditorTemplates/DevicesToFiscalizationViewModel.cshtml", fsc);
}
========================================================================
I'm trying to write this using EditorFor but I have a problem with correctly writing the CustomerChanged method that returns a list of Devices and not expecting this EditorTemplate that looks as follows:
#model TestMVC.ViewModels.DevicesToFiscalizationViewModel
<tr>
<td style="text-align:center">
<div class="checkbox">
#Html.EditorFor(m => m.Selected)
</div>
</td>
<td>
#Html.DisplayFor(m => m.DeviceName)
</td>
<td>
#Html.DisplayFor(m => m.SerialNumber)
</td>
<td>
#Html.DropDownListFor(m => m.LocalizationId, new SelectList(Model.Localizations, "LocalizationId", "Name"), "Select", htmlAttributes: new { #class = "form-control", style = "width: 200px;" })
</td>
<td>
#Html.HiddenFor(m => m.DeviceId)
</td>
</tr>
Main View:
<table class="table" id="devicesToFiscalization">
...
<tbody>
#Html.EditorFor(m => m.Devices)
</tbody>
</table>

Related

Getting the id of a table row and passing it to a event handler when a checkbox is checked

This question was posted almost eight years ago, (Question 16853364). When I follow the answer that was provided the code runs but the the value of the record Id is not past to the event handler. I'm using ASP.NET Core 3.1 creating a Razor Page application. I assume the original question was based on using ASP.NET 2.x and MVC, Hopefully that is why it is not working for me.
The Index page is a list to task that a user has been assigned. I've added a checkbox to the page; after completing the user checks the checkbox. Checking the box should trigger an event handler that will update a column (tinyInt) in the database. Checking the checkbox does call the event handler, however the code that should get the record's Id before call the handler does not get the Id. My code on the Index.cshtml that displays the records and calls the handler is:
...
#if (Model.ScheduleOut.Count() == 0)
{
<p>
This speaker has not been scheduled in the past six weeks or in the next 6 months...
</p>
}
else
{
<table class="table table-striped border" height="40">
<tr class="table-secondary">
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "Id:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "DOT:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "Talk #'s:")
</th>
<th>
#Html.DisplayNameFor(m => m.SchedOutgoingVM.CongObj.CongName)
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM, "Date / Time:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM, "Talk Coordinator:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM, "TC's Phone:")
</th>
<th></th>
<th></th>
</tr>
<!-- *************** display records ***************-->
#foreach (var item in Model.ScheduleOut)
{
<tr id="1">
<td class="tdBorder">
#Html.DisplayFor(m => item.Id)
</td>
<td>
#Html.DisplayFor(m => item.DOT)
</td>
<td>
#Html.DisplayFor(m => item.SpkTalkNum)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.CongName)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.MtgDay) / #Html.DisplayFor(m => item.Congregation.MtgTime)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.tcFirstName) #Html.DisplayFor(m => item.Congregation.tcLastName)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.tcMobilePhone)
</td>
<td>
<!-- check or uncheck checkbox based on value in database -->
#if (item.Accepted == null || item.Accepted == 0)
{
<!-- if false in the database-->
<input asp-for="AcceptedBoolean" type="checkbox" onclick="ckBox(this)" form-check-input">
}
else
{
<!-- if true in the database-->
<input asp-for="AcceptedBoolean" type="checkbox" checked="checked" onclick="ckBox(this)" form-check-input">
}
</td>
</tr>
}
</table>
}
...
The Index.cshtml.cs
...
//********** OnPost - update Accepted (tinyInt)column in database (using id) **********
public async Task<JsonResult> OnGetUpDateAccepted(int id)
{
///code to update database
return new JsonResult(id);
}
...
Any suggestion on what I'm doing wrong or missing is greatly appreciated. If you have a better way of updating the database I would like to know that also.
Javascript Code:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
function ckBox(e) {
if (e.checked) {
////var Testt = $(this).closest('tr').attr('Testtid');
////alert('Got this far...')
var id = $(this).closest('tr').attr('id');
console.log(Testt); //used to capture the value at this point
alert(id);
$.getJSON('?handler=UpDateAccepted&Testt=' + id, (data) => {
});
} else {
var Testt = 0
alert('bye');
$.getJSON('?handler=UpDateAccepted&Testt=' + id, (data) => {
});
}
}
</script>
Firstly,you comment on some js code when you debug the code,and this caused the js in a mess.The variable name is not correct in your sample.Be careful with theses variables.
Then,the backend paramater named id,so the request url should be:'?handler=UpDateAccepted&id=' + id instead of
'?handler=UpDateAccepted&Testt=' + id.
Finally,you want to pass the id of the record,but all of the tr have the same id.You need change <tr id="1"> to:<tr id="#item.Id">.
Here is my whole working demo:
Model:
public class ScheduleOut
{
public int Id { get; set; }
public string DOT { get; set; }
public string SpkTalkNum { get; set; }
public int? Accepted { get; set; }
public Congregation Congregation { get; set; }
}
public class Congregation
{
public string CongName { get; set; }
public string MtgTime { get; set; }
public string tcFirstName { get; set; }
public string tcLastName { get; set; }
public string tcMobilePhone { get; set; }
public int MtgDay { get; set; }
}
public class SchedOutgoingVM
{
public ScheduleOut ScheduleOutObj { get; set; }
public Congregation CongObj { get; set; }
}
Index.cshtml:
#page
#model IndexModel
#if (Model.ScheduleOut.Count() == 0)
{
<p>
This speaker has not been scheduled in the past six weeks or in the next 6 months...
</p>
}
else
{
<table class="table table-striped border" height="40">
<tr class="table-secondary">
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "Id:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "DOT:")
</th>
<th>
#Html.LabelFor(m => m.SchedOutgoingVM.ScheduleOutObj, "Talk #'s:")
</th>
//...
<th>
#Html.LabelFor(m => m.SchedOutgoingVM, "TC's Phone:")
</th>
<th></th>
<th></th>
</tr>
#foreach (var item in Model.ScheduleOut)
{
<!-- *************** change here ***************-->
<tr id="#item.Id">
<td class="tdBorder">
#Html.DisplayFor(m => item.Id)
</td>
<td>
#Html.DisplayFor(m => item.DOT)
</td>
<td>
#Html.DisplayFor(m => item.SpkTalkNum)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.CongName)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.MtgDay) / #Html.DisplayFor(m => item.Congregation.MtgTime)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.tcFirstName) #Html.DisplayFor(m => item.Congregation.tcLastName)
</td>
<td>
#Html.DisplayFor(m => item.Congregation.tcMobilePhone)
</td>
<td>
#if (item.Accepted == null || item.Accepted == 0)
{
<input asp-for="AcceptedBoolean" type="checkbox" onclick="ckBox(this)" form-check-input">
}
else
{
<input asp-for="AcceptedBoolean" type="checkbox" checked="checked" onclick="ckBox(this)" form-check-input">
}
</td>
</tr>
}
</table>
}
JS in Index.cshtml:
#section Scripts
{
<script type="text/javascript">
function ckBox(e) {
if (e.checked) {
var id = $(e).closest('tr').attr('id');
$.getJSON('?handler=UpDateAccepted&id=' + id, (data) => {
});
} else {
//do your stuff...
}
}
</script>
}
Result:
BTW,if User unchecked the checkbox,it seems also need to get the id to update.If what I guess is correct,you need change like below:
<script type="text/javascript">
function ckBox(e) {
var id = $(e).closest('tr').attr('id');
$.getJSON('?handler=UpDateAccepted&id=' + id, (data) => {
});
}
</script>

sending values to url string on dropdown selected value change in asp.net MVC

I have two tables. First is Development region and second is Zone. Zone has got RegionID as a foreign key. I would like to populate all the row from Zone table that is related with the Region selected from the dropdown list. I cannot figure out why the value is not being passed in url string. Please help me out and suggest the best way to accomplish it. Below are the models, controllers and view.
Model Zone
public class Zone
{
[Key]
public int ZoneID { get; set; }
[Required]
[Display(Name = "Zone Code")]
[RegularExpression(#"^[a-zA-Z]*$"), StringLength(5, ErrorMessage = "Code cannot be more than 5 charachter long")]
[Column("ZCode")]
public string ZoneCode { get; set; }
[Display(Name ="Zone"),RegularExpression(#"^[A-Z]+[a-z]*$"),Required]
public string ZoneName { get; set; }
public int RegionID { get; set; }
public virtual DevRegion devregion { get; set; }
[Required]
[Display(Name ="Active")]
public Boolean isActive { get; set; }
}
Model DevRegions
public class DevRegion
{
[Key]
public int RegionID { get; set; }
[Required]
[Display(Name = "Code")]
[RegularExpression(#"^[a-zA-Z]*$"), StringLength(5, ErrorMessage = "Code cannot be more than 5 charachter long")]
[Column("RCode")]
public string RegionCode { get; set; }
[Required]
[Display(Name ="Region")]
[Column("RName")]
[RegularExpression(#"^[A-Z]+[a-zA-Z\s-]*$", ErrorMessage ="Region can only consist of alphabets, space and dash")]
[StringLength(30,ErrorMessage ="Region cannot exceed 30 characters")]
public string RegionName { get; set; }
[Required]
[Display(Name ="Active")]
public Boolean isActive { get; set; }
}
ZonesController
public class ZonesController : Controller
{
private HuRISContext db = new HuRISContext();
// GET: Zones
public ActionResult Index(int? id)
{
ViewBag.RegionID = new SelectList(db.DevRegions, "RegionID", "RegionName");
var zones = db.Zones.Include(z => z.devregion).Where(x=>x.RegionID==(int)(id??x.RegionID));
return View(zones.ToList());
}
Index.cshtml
#model IEnumerable<HuRIS.Models.Zone>
....
<p>#Html.ActionLink("Create New", "Create")</p>
#using (Html.BeginForm("Index","Zones",FormMethod.Get))
{
#Html.AntiForgeryToken();
<div class="panel panel-info">
<div class="panel-body">
<div class="form-group center-block">
<label for="RegionID" class="control-label">Region:</label>
#Html.DropDownList("RegionID", null, "Show all Zones", htmlAttributes: new { #class = "form-control", #onchange = "this.form.submit();" })
</div>
</div>
</div>
}
<table class="table">
<tr>
<th>#Html.DisplayNameFor(model => model.devregion.RegionName)</th>
<th>#Html.DisplayNameFor(model => model.ZoneCode)</th>
<th>#Html.DisplayNameFor(model => model.ZoneName)</th>
<th>#Html.DisplayNameFor(model => model.isActive)</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.devregion.RegionName</td>
<td>#Html.DisplayFor(modelItem => item.ZoneCode)</td>
<td>#Html.DisplayFor(modelItem => item.ZoneName)</td>
<td>#Html.DisplayFor(modelItem => item.isActive)</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ZoneID }) |
#Html.ActionLink("Details", "Details", new { id = item.ZoneID }) | #Html.ActionLink("Delete", "Delete", new { id = item.ZoneID })
</td>
</tr>
}
</table>
JQuery
$(document).ready(function () {
$(".form-control").change(function () {
$.ajax({
url: "~/ZonesController/Index",
type: 'GET',
cache: false,
data: { RegionID: $(".form-control").val() },
success: function (data) {
}
});
});
});
index method as shown below
public ActionResult Index()
{
ViewBag.Region = new SelectList(db.DevRegions, "RegionID", "RegionName");
var allzones = db.Zones.Include(z => z.devregion);
return View(allzones.ToList());
}
create new method as Shown below to accept the id selected on dropdown change event and create the partial view to load on the list of data from the table depending on what is selected on the dropdown:
public ActionResult ZoneList(int? id)
{
var zones = db.Zones.Include(z => z.devregion).Where(x => x.RegionID == (int)(id ?? x.RegionID));
return PartialView("~/Views/PartialViews/_ZoneList.cshtml", zones.ToList());
}
changed javascript as follows.
$("#Region").change(function () {
var selectedID = $(this).val();
$.get('/Zones/ZoneList/' + selectedID, function (data) {
$('.table').html(data);
//$('.table').fadeOut("linear");
//$('.table').fadeIn("linear");
});
});
});
on the index.cshtml changed the code as follows:
#model IEnumerable<HuRIS.Models.Zone>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
<div class="panel panel-info">
<div class="panel-body">
<div class="form-group center-block">
<label for="RegionID" class="control-label">Region:</label>
#Html.DropDownList("Region", null, "Show all Zones", htmlAttributes: new { #class = "form-control"})
</div>
</div>
</div>
}
#{
Html.RenderPartial("~/Views/PartialViews/_ZoneList.cshtml", Model);
}
partial view _ZoneList.cshtml
#model IEnumerable<HuRIS.Models.Zone>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.devregion.RegionName)
</th>
<th>
#Html.DisplayNameFor(model => model.ZoneCode)
</th>
<th>
#Html.DisplayNameFor(model => model.ZoneName)
</th>
<th>
#Html.DisplayNameFor(model => model.isActive)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.devregion.RegionName)
</td>
<td>
#Html.DisplayFor(modelItem => item.ZoneCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.ZoneName)
</td>
<td>
#Html.DisplayFor(modelItem => item.isActive)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ZoneID }) |
#Html.ActionLink("Details", "Details", new { id = item.ZoneID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.ZoneID })
</td>
</tr>
}

Null values in HTML form from MVC view

I would like to save users steps and have each row in my MVC index view be a form.
I am using the HTML.Begin form inside the foreach to build the individual forms. The page renders without errors and the form submits without problems, but I can not bind to a model inside the controller - thus all the submitted data is lost.
I have validated that the form values are there to begin with when the form is submitted: item.Completed=true&item.Completed=false&item.Comment=HELLO+WORLD+&item.FinalizeStepId=1&item.StepId=1, but the controller does not accept them and the FinalizeStepViewModel object is created with null values.
So how do I get the form to pass back the data correctly?
This might be my second question ever on Stackoverflow, so let me know what additional information I might need to add.
Thanks.
=== Model =====
public class FinalizeStepViewModel
{
public int FinalizeStepId { get; set; }
// foreign key from Step table
public int StepId { get; set; }
// name of taks from Step table
public string StepDesc { get; set; }
[DisplayName("Review Data")]
public string ReviewFormulaValue { get; set; }
[Required]
public bool Completed { get; set; }
[DisplayName("Fiscal Year")]
public int FiscalYear { get; set; }
// Period for the adjustment
[Required]
public int Period { get; set; }
[Required]
public string UserID { get; set; }
[Required]
[DisplayName("Created By")]
public string CreatedBy { get; set; }
[Required]
[DisplayName("Created At")]
public DateTime CreatedAt { get; set; }
public string Comment { get; set; }
==== View ==========
#model IEnumerable
#{
ViewBag.Title = "Index";
// is everything completed, if yes => enabled
string alldone = "enabled";
}
<h2>Finalize Checklist</h2>
<table class="table">
<tr>
<th>
Completed
</th>
<th>
Finalized Task
</th>
<th>
Review Data
</th>
<th>
#Html.DisplayNameFor(model => model.Comment)
</th>
<th></th>
<th></th>
#*<th>
#Html.DisplayNameFor(model => model.FiscalYear)
</th>
<th>
#Html.DisplayNameFor(model => model.Period)
</th>
<th>
#Html.DisplayNameFor(model => model.CreatedBy)
</th>
<th>
#Html.DisplayNameFor(model => model.CreatedAt)
</th>
<th>
#Html.DisplayNameFor(model => model.UserID)
</th>*#
<th></th>
</tr>
#foreach (var item in Model)
{
//<form action="/FinalizeSteps/Checklist/" method="post">
//#using (Html.BeginForm("Login", "Account", FormMethod.Post))
//// <form action="/Account/Login" action="post">
using (Html.BeginForm("EditFromChecklist", "FinalizeSteps", FormMethod.Post, new { finalizeStepPassed = Model }))
{
<tr>
<td>
<div class="form-group" style="text-align: center; vertical-align: text-top;">
<div class="checkbox">
#Html.EditorFor(modelItem => item.Completed)
#if (item.Completed == false) { alldone = "disabled"; }
</div>
</div>
</td>
<td>
<h4>#Html.DisplayFor(modelItem => item.StepDesc)</h4>
</td>
<td style="text-align: center;">
#Html.DisplayFor(modelItem => item.ReviewFormulaValue)
</td>
<td>
<div class="form-group" style="width: 300px;">
#Html.EditorFor(modelItem => item.Comment, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.Comment, "", new { #class = "text-danger" })
</div>
</td>
<td>
<div class="form-group">
#Html.EditorFor(modelItem => item.FinalizeStepId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.FinalizeStepId, "", new { #class = "text-danger" })
</div>
</td>
<td>
<div class="form-group">
#Html.EditorFor(modelItem => item.StepId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.FinalizeStepId, "", new { #class = "text-danger" })
</div>
</td>
#*<td>
#Html.DisplayFor(modelItem => item.FiscalYear)
</td>
<td>
#Html.DisplayFor(modelItem => item.Period)
</td>
<td>
#Html.DisplayFor(modelItem => item.CreatedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.CreatedAt)
</td>
<td>
#Html.DisplayFor(modelItem => item.UserID)
</td>*#
<td>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
#Html.ActionLink("Save", "EditFromChecklist", new { FinalizeStepId = item.FinalizeStepId, StepId = item.StepId, Completed = item.Completed, Comment = item.Comment })
#*#Html.ActionLink("Edit", "Edit", new { id = item.FinalizeStepId }) |
#Html.ActionLink("Details", "Details", new { id = item.FinalizeStepId }) |
#Html.ActionLink("Delete", "Delete", new { id = item.FinalizeStepId })*#
</td>
</tr>
}
}
</table>
=== Controller Method ====
[HttpPost]
public ActionResult EditFromChecklist([Bind(Include = "FinalizeStepId,StepId,Completed,Comment")] FinalizeStepViewModel finalizeStepPassed)
{
// Do we have a FinalizeStepId?
if (finalizeStepPassed.FinalizeStepId != 0)
{
// Yes, this is an edit
...
Change your EditFromChecklist action's parameter from finalizeStepPassed to item.
Or you could use a partial view to submit your data.
_FinalizeStepPartial.cshtml
#model FinalizeStepViewModel
using (Html.BeginForm("EditFromChecklist", "FinalizeSteps"))
{
#Html.EditorFor(model => model.Completed)
// rest of your form
}
and in the main view inside of loop call the partial
#foreach (var item in Model)
{
#Html.Partial("_FinalizeStepPartial",item)
}
You should bind to a IList instead of IEnumerable, and instead of
#foreach (var item in Model)
{
#Html.EditorFor(modelItem => item.Completed)
}
Use this syntax
#for( int i=0; i < Model.Count; i++ )
{
#Html.EditorFor(modelItem => Model[i].Completed)
}
Here is an earlier topic that also discussed this: How to pass IEnumerable list to controller in MVC including checkbox state?

MVC Enum Model Binding in For Loop

I have an MVC 5 app where I am using a for loop so I can bind a collection when passing back to the controller. This works fine for all my properties except for the one that is based on a DropDownFor type.
The problem is the name of the property is not getting set to "product.[0].TypeOfSubscription.
I have tried 3 different ways: The first 2 method end up with a name of [0].TypeOfSubscription and the 3rd one does have the correct name product[0].TypeOfSubscription but there is no binding occuring when I pass it back to the controller.
I think the problem is that the 3rd option is binding but because it is hidden it is not getting the selected value assigned.
#Html.EnumDropDownListFor(modelItem => Model[i].TypeOfSubscription)
#Html.EnumDropDownListFor(modelItem => Model[i].TypeOfSubscription,
new { name = "product[" + #i + "].TypeOfSubscription"})
#Html.Hidden("product[" + #i + "].TypeOfSubscription",
Model[i].TypeOfSubscription)
Model
public class VmStoreProducts
{
public VmStoreProducts()
{
NoOfUsers = 1;
}
public enum SubscriptionType
{
Monthly,
Annual
}
public int MojitoProductId { get; set; }
[Display(Name = "Category")]
public string ProductCategory { get; set; }
public virtual string Name { get; set; }
public string Description { get; set; }
[Display(Name = "Image")]
public byte[] ImageData { get; set; }
[Display(Name = "Type of Subscription")]
public SubscriptionType TypeOfSubscription { get; set; }
public decimal Price { get; set; }
[Display(Name = "No. of Users")]
public int NoOfUsers { get; set; }
[Display(Name = "Total Price")]
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal TotalPrice { get; set; }
}
For Loop - View
#model PagedList.IPagedList<VmStoreProducts>
#using Mojito.Domain
#using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Mojito Products</h2>
<div class="col-md-9"></div>
<div class="col-md-3">
#using (Html.BeginForm("Index", "MojitoProducts", FormMethod.Get))
{
<p>
#Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
<input type="submit" value="Search" />
</p>
}
</div>
#using (Html.BeginForm("AddToCart", "ShoppingCart", FormMethod.Post))
{
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().ImageData)
</th>
<th>
#Html.ActionLink("Category", "Index", new { sortOrder = ViewBag.SortByCategory, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Product", "Index", new { sortOrder = ViewBag.SortByProduct, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().Description)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().TypeOfSubscription)
</th>
<th>
#Html.ActionLink("Price", "Index", new { sortOrder = ViewBag.SortByPrice, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().NoOfUsers)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().TotalPrice)
</th>
<th></th>
</tr>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#if (Model[i].ImageData != null)
{
<div class="pull-left" style="margin-right: 10px">
<img class="img-thumbnail" width="75" height="75"
src="#Url.Action("GetImage", "MojitoProducts",
new { Model[i].MojitoProductId })" />
</div>
}
</td>
<td>
#Html.DisplayFor(modelItem => Model[i].ProductCategory)
</td>
<td>
#Html.TextBox("product[" + #i + "].Name",
Model[i].Name, new { #readonly = "readonly" })
</td>
<td>
#Html.DisplayFor(modelItem => Model[i].Description)
</td>
<td>
#Html.EnumDropDownListFor(modelItem => Model[i].TypeOfSubscription)
#Html.EnumDropDownListFor(modelItem => Model[i].TypeOfSubscription,
new { name = "product[" + #i + "].TypeOfSubscription"})
#Html.TextBox("product[" + #i + "].TypeOfSubscription",
Model[i].TypeOfSubscription, new { hidden=true })
</td>
<td>
#Html.TextBox("product[" + #i + "].Price",
Model[i].Price, new { #readonly = "readonly", style = "width:50px" })
</td>
<td>
#Html.TextBox("product[" + #i + "].NoOfUsers",
Model[i].NoOfUsers, new { type = "number", min = "0", style = "width:50px" })
</td>
<td>
#Html.TextBox("product[" + #i + "].TotalPrice",
Model[i].TotalPrice, new { style = "width:50px" })
</td>
<td>
<div class="pull-right">
#if (Request.Url != null)
{
#Html.Hidden("product[" + #i + "].MojitoProductId",
Model[i].MojitoProductId)
#Html.Hidden("returnUrl", Request.Url.PathAndQuery)
}
</div>
</td>
</tr>
}
<tr>
<td colspan="6">
<div class="pull-right">
<input type="submit" class="btn btn-success" value="Add to cart" />
</div>
</td>
</tr>
</table>
}
Controller Method
public ActionResult AddToCart(List<VmStoreProducts> product, string returnUrl)
{
ShoppingCart cartObjects = (Session["CartObjects"] as ShoppingCart) ?? new ShoppingCart();
Session["CartObjects"] = cartObjects;
foreach (var item in product)
{
if (item.NoOfUsers > 0)
{
cartObjects.AddItem(item);
}
}
return RedirectToAction("Index", new { returnUrl });
}
Move the definition of the enum outside the VmStoreProducts class
public enum SubscriptionType
{
Monthly,
Annual
}
public class VmStoreProducts
{
public VmStoreProducts()
{
NoOfUsers = 1;
}
public int MojitoProductId { get; set; }
....
}
The for loop will name the selects
[0].TypeOfSubscription
[1].TypeOfSubscription
....
which will correctly bind on postback (assuming your action method is public ActionResult AddToCart(IEnumerable<VmStoreProducts> products) {...
Also, do not use
#Html.TextBox("product[" + #i + "].Name", Model[i].Name, new { #readonly = "readonly" })
Since you already using a DisplayFor for the same property a hidden input seems more appropriate, so
#Html.HiddenFor(m => m[i].Name)
or if you want to display it twice
#Html.TextBoxFor(m => m[i].Name, new { #readonly = "readonly" })
This applies to the other properties as well
Try using a textbox and hide it to persist the value or use another 'data- property
In case of DropDownListFor, when data is posted back to controller, selected value get lost, so we need to have a hidden textbox to keep the selected value

DisplayNameFor - I have a View model and i;m not sure how to implement with that

I have a view model:
public class SitesListViewModel
{
public IEnumerable<Site> Sites { get; set; }
public PagingInfo PagingInfo { get; set; }
}
I have a view:
#model .....WebUI.Models.SitesListViewModel
<table class="table table-striped">
<tr>
<th width="100%">
#Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
<th></th>
<th></th>
</tr>
#foreach (var item in Model.Sites)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>#Html.EditButton("Edit", Url.Action("Edit", new { siteID = item.SiteID }), new ButtonRequirement { ExtraClasses = "btn-xs" })</td>
<td>#Html.ViewButton("View", Url.Action("Details", new { siteID = item.SiteID }), new ButtonRequirement { ExtraClasses = "btn-xs" })</td>
<td>
#using (Html.BeginForm("Delete", "Site"))
{
#Html.Hidden("siteID", item.SiteID)
#Html.DeleteButton("Delete", new ButtonRequirement { ExtraClasses = "btn-xs" })
}
</td>
</tr>
}
</table>
this line needs to know that it is working on the Model.Sites.Name rather than just model.Name.. how do I do this:
#Html.DisplayNameFor(model => model.Name)
?
I imagined it would be something like:
#Html.DisplayNameFor(model => model.Sites.Name)
but it doesn't work:
The type arguments for method 'System.Web.Mvc.Html.DisplayNameExtensions.DisplayNameFor<TModel,TValue>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,TValue>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Use a List<> indead of a IEnumerable<> in your viewmodel and a for loop should do the trick.
#for (int i = 0; i < Model.Sites.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Sites[i].Name)
</td>
<td>#Html.EditButton("Edit", Url.Action("Edit", new { siteID = Model.Sites[i].SiteID }), new ButtonRequirement { ExtraClasses = "btn-xs" })</td>
<td>#Html.ViewButton("View", Url.Action("Details", new { siteID = Model.Sites[i].SiteID }), new ButtonRequirement { ExtraClasses = "btn-xs" })</td>
<td>
#using (Html.BeginForm("Delete", "Site"))
{
#Html.Hidden("siteID", Model.Sites[i].SiteID)
#Html.DeleteButton("Delete", new ButtonRequirement { ExtraClasses = "btn-xs" })
}
</td>
</tr>
}

Categories

Resources