Saving multiple objects from MVC view - c#

I'm writing my first MVC3 application which is a simple order tracking application. I would like to edit the order and the details at the same time. When I edit the order the ActionResult for the Edit returns the order and the associated line (i'm using EF as well).
public ActionResult Edit(int id)
{
// Get the order with the order lines
var orderWithLines = from o in db.Orders.Include("OrderLines")
where o.ID == id
select o;
// Not sure if this is the best way to do this.
// Need to find a way to cast to "Order" type
List<Order> orderList = orderWithLines.ToList();
Order order = orderList[0];
// Use ViewData rather than passing in the object in the View() method.
ViewData.Model = order;
return View();
}
The order and the lines display with no issue but when I save the page I do not get any of the lines passed back to the controller. Only the order. Here is the View code.
#model OrderTracker.Models.Order
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Order</legend>
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.UserId)
<div>
#Html.LabelFor(model => model.OrderDate)
</div>
<div>
#Html.EditorFor(model => model.OrderDate)
</div>
<div>
#Html.LabelFor(model => model.Description)
</div>
<div>
#Html.EditorFor(model => model.Description)
</div>
<table>
<tr>
<th>
Description
</th>
<th>
Quantity
</th>
<th>
Weight
</th>
<th>
Price
</th>
<th></th>
</tr>
#foreach (var line in Model.OrderLines)
{
<tr>
<td>
#Html.EditorFor(modelItem => line.Description)
</td>
<td>
#Html.EditorFor(modelItem => line.Quantity)
</td>
<td>
#Html.EditorFor(modelItem => line.Weight)
</td>
<td>
#Html.EditorFor(modelItem => line.Price)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Can I please get some guidance on the best way to save the line data as well as the order data.
Thanks.

The issue that you are facing is related to the names generated by the ICollection<T> controls. Here is a detailed discussion by Phil Haack and a solution by him (in terms of an #Html extension method; download the sample project from the link given at the end of his blog post). This post targets MVC/MVC2; however it is still applicable with MVC3.
Alternatively if you don't want to follow the hack, you can opt for a EditorTemplate for your OrderLine entity model.
Here are the steps.
1) Create Editor template under (Views ->Shared -> EditorTemplates -> OrderLine.cshtml)
It is important to create a folder named EditorTemplates under Shared, and the template name should be same as the EntityModel for which you want to create the templete; hence the name OrderLine.cshtml)
2) Code for OrderLine.cshtml
#model OrderTracker.Models.OrderLine
#{
Layout = null;
}
<!DOCTYPE html>
#Html.HiddenFor(modelItem => Model.id)
<tr>
<td>
#Html.EditorFor(modelItem => Model.Description)
</td>
<td>
#Html.EditorFor(modelItem => Model.Quantity)
</td>
<td>
#Html.EditorFor(modelItem => Model.Weight)
</td>
<td>
#Html.EditorFor(modelItem => Model.Price)
</td>
</tr>
3) Edit your View with this code (note that I've used EditorFor for OrderLines collection)
#model OrderTracker.Models.Order
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Order</legend>
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.UserId)
<div>
#Html.LabelFor(model => model.OrderDate)
</div>
<div>
#Html.EditorFor(model => model.OrderDate)
</div>
<div>
#Html.LabelFor(model => model.Description)
</div>
<div>
#Html.EditorFor(model => model.Description)
</div>
<div>
<table>
<tr>
<th>
Description
</th>
<th>
Quantity
</th>
<th>
Weight
</th>
<th>
Price
</th>
</tr>
#Html.EditorFor(model => model.OrderLines)
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
4) Now on post back you will see the values

Using MVC it should be rather straight forward as the framework is designed to to turn a form into a model.
[HttpGet]
public ActionResult Edit(int id)
{
// (you can probably rewrite this using a lambda
var orderWithLines = from o in db.Orders.Include("OrderLines")
select o;
// Use ViewData rather than passing in the object in the View() method.
ViewData.Model = orderWithLines.FirstOrDefault(x => x.ID = id);
return View();
}
[HttpPost]
public ActionResult Edit(OrderTracker.Models.Order model)
{
if (ModelState.IsValid)
{
// call the service layer / repository / db to persist the object graph
_service.Save(model); // this assumes your view models are the same as your domain
}
}

The issue is with your foreach if you look at the raw html that it produces it will not be generating unique ids for each of the order lines and thus will not be able to bind the model when the form is posted back.
Change the foreach to a for loop and then reference each orderline using the index. This will allow for unique ids to be generated in the form and allow you to bind the to the model when it is posted back.
e.g.
#for (var counter = 0; counter < Model.OrderLines.Count(); counter++)
{
<tr>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Description)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Quantity)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Weight)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Price)
</td>
</tr>
}
</table>

Related

How to validate divisible of TextBox in MVC asp.net

Is it possible validating textbox in this case? The value in this TextBox ( #Html.TextBoxFor(model => model.FirstSetList[i].Amount)) must be divisible by another value - Pack in this case (#Html.DisplayFor(model => model.FirstSetList[i].Pack)).
I tried to use Fluent Validation, but there is a problem with client-side validation. Maybe it can be done without fluent validation. Only with basic DataAnnotation?
Here is the code of view:
#model FP.WebUI.ViewModels.DataItemVm
<h3>
What kind of currencies you currently own?
</h3>
<br />
<div>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstSetList.FirstOrDefault().Name)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstSetList.FirstOrDefault().Pack)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstSetList.FirstOrDefault().Amount)
</th>
</tr>
#using (Html.BeginForm())
{
for (var i = 0; i < Model.FirstSetList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.FirstSetList[i].Name)
#Html.HiddenFor(model => model.FirstSetList[i].Name)
</td>
<td>
#Html.DisplayFor(model => model.FirstSetList[i].Pack)
#Html.HiddenFor(model => model.FirstSetList[i].Pack)
</td>
<td>
#Html.TextBoxFor(model => model.FirstSetList[i].Amount)
</td>
</tr>
}
<input type="submit" value="Confirm" class="btn btn-success" />
}
</table>
</div>
UPDATE
I was trying to use Remote Annotation, however chrome doesn't send any information to my JsonResult Method and i don't know why.
My ViewModel:
.
.
.
[Remote("Divisibility", "Account", HttpMethod = "POST", ErrorMessage = "Value is incorrect.")]
public int Amount { get; set; }
.
.
.
My View:
.
.
.
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm())
{
for (var i = 0; i < Model.FirstSetList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.FirstSetList[i].Name)
#Html.HiddenFor(model => model.FirstSetList[i].Name)
</td>
<td>
#Html.DisplayFor(model => model.FirstSetList[i].Pack)
#Html.HiddenFor(model => model.FirstSetList[i].Pack)
</td>
<td>
#Html.TextBoxFor(model => model.FirstSetList[i].Amount)
#Html.ValidationMessageFor(model => model.FirstSetList[i].Amount)
</td>
</tr>
}
<input type="submit" value="Confirm" class="btn btn-success" />
}
</table>
</div>
My Controller:
[HttpPost]
public JsonResult Divisibility(int valueToCheck)
{
var value = User.Identity.GetUserId().Where(x => x.Equals("qqqqq"));
//I know that this condition does not make sense, but this was only for test.
//Anyway like i said, chrome doesn't send anything to this method.
return Json(value == null);
}
The longer solution, and maybe better, would be to create your own validator in javascript using the same validators that MVC is using. But, to something someone else mentioned, server side validation. Use DataAnnotations with the Remote attribute and you can accomplish the same.
http://www.tugberkugurlu.com/archive/asp-net-mvc-remote-validation-for-multiple-fields-with-additionalfields-property
You will create a server method to do the validation but the front end will be calling it. The link I provided should give you all that you need.

MVC Partial View Will Not Load

I keep getting an error message along the lines of:
..but this dictionary requires a model item of type..
My view I'm trying to put the partial view into is:
#using TheNovelMachine.Models
#model TheNovelMachine.Models.Account
#{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<img id="homeimage" src="~/Image/front-page-book.jpg" alt="The Novel Machine" />
<div id="text" class="col-lg-12">
<h1>Profile</h1>
<div class="col-lg-6 no-gutter">
<h2>#Html.DisplayFor(model => model.Username)</h2>
<h4>#Html.DisplayFor(model => model.FullName)</h4>
<p>#Html.DisplayFor(model => model.Email)</p>
</div>
<div class="col-lg-6 pull-right text-right no-gutter">
<img class="profilepic" src="~/Image/#(Html.DisplayFor(model => model.Username)).jpg" />
</div>
<div class="col-lg-12 no-gutter">
<hr/>
<h4>#Html.DisplayFor(model => model.Username)'s Novels</h4>
#{
Html.RenderPartial("~/Views/Shared/_NovelProfile.cshtml");
}
</div>
</div>
And the Partial View is:
#using System.Web.DynamicData
#using TheNovelMachine.Models
#model IEnumerable<TheNovelMachine.Models.Novel>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.Genre)
</th>
<th>
#Html.DisplayNameFor(model => model.Abstract)
</th>
<th>
#Html.DisplayNameFor(model => model.Privacy)
</th>
<th>
#Html.DisplayNameFor(model => model.AccountId)
</th>
</tr>
#foreach (var item in Model.Where(i => i.AccountId.ToString() == Url.RequestContext.RouteData.Values["id"].ToString())) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
#Html.DisplayFor(modelItem => item.Abstract)
</td>
<td>
#Html.DisplayFor(modelItem => item.Privacy)
</td>
<td>
#Html.DisplayFor(modelItem => item.AccountId)
</td>
</tr>
}
</table>
Your partial view is expecting this model (you are declaring it at the top of you partial view)
#model IEnumerable<TheNovelMachine.Models.Novel>
You should pass that model in this line:
#{
Html.RenderPartial("~/Views/Shared/_NovelProfile.cshtml", Model.Novels);
}
That should work, assuming Model.Novels is of this type IEnumerable<TheNovelMachine.Models.Novel> or it can be converted to that type.
Here you have an example with more details, but the idea is the same. If you don't pass this as a parameter, then the partial view can't be constructed (that's why you are getting that error).
MSDN Documentation here for RenderPartial method.
So even though there are other answers as to what you need to do, here is what is actually happening, so you know in the future.
#Html.RenderPartial("_NovelProfile");
would be the same code as:
#Html.RenderPartial("_NovelProfile", Model)

Model Collections Lost When Form Submitted

Using the post request below the model returns null for both the collections yet it correctly returns the boolean attribute. My expectation was that the collections loaded into the model during the get request would persist to the post request. What am I missing?
EDIT: Essentially I am trying to update the list of invoices based on the users selection of a selectlist and a checkbox.
Controller:
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> Index(bool displayFalse = true)
{
InvoiceViewModel invoiceView = new InvoiceViewModel();
var companies = new SelectList(await DbContext.Company.ToListAsync(), "CompanyID", "Name").ToList();
var invoices = await DbContext.Invoice.Where(s => s.Paid.Equals(displayFalse)).ToListAsync();
return View(new InvoiceViewModel { Companies = companies,Invoices = invoices, SelectedCompanyID = 0, DisplayPaid = displayFalse});
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Index(InvoiceViewModel model)
{
model.Invoices = await DbContext.Invoice.Where(s => s.CompanyID.Equals(model.SelectedCompanyID) && s.Paid.Equals(model.DisplayPaid)).ToListAsync();
return View(model);
}
Model:
public class InvoiceViewModel
{
public int SelectedCompanyID { get; set; }
public bool DisplayPaid { get; set; }
public ICollection<SelectListItem> Companies { get; set; }
public ICollection<Invoice> Invoices{ get; set; }
}
View:
#model InvoiceIT.Models.InvoiceViewModel
<form asp-controller="Billing" asp-action="Index" method="post" class="form-horizontal" role="form">
<label for="companyFilter">Filter Company</label>
<select asp-for="SelectedCompanyID" asp-items="Model.Companies" name="companyFilter" class="form-control"></select>
<div class="checkbox">
<label>
<input type="checkbox" asp-for="DisplayPaid" />Display Paid
<input type="submit" value="Filter" class="btn btn-default" />
</label>
</div>
<br />
</form>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().InvoiceID)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().CompanyID)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().Description)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().InvoiceDate)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().DueDate)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().Paid)
</th>
<th></th>
</tr>
#foreach (var item in Model.Invoices)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.InvoiceID)
</td>
<td>
#Html.DisplayFor(modelItem => item.CompanyID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.InvoiceDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.DueDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.Paid)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.InvoiceID }) |
#Html.ActionLink("Details", "Index", "InvoiceItem", new { id = item.InvoiceID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.InvoiceID })
</td>
</tr>
}
</table>
A form only posts back the name/value pairs of its controls (input, textarea, select). Since the only 2 controls you generate are for the SelectedCompanyID and DisplayPaid properties of your model, then only those properties will be bound when post.
From your comments, what your really wanting to do is to update the table of invoices based on the values of the selected company and the checkbox.
From a performance point of view, the approach is to use ajax to update just the table of invoices based on the value of your controls.
Create a new controller method that return a partial view of the table rows
public PartialViewResult Invoices(int CompanyID, bool DisplayPaid)
{
// Get the filtered collection
IEnumerable<Invoice> model = DbContext.Invoice.Where(....
return PartialView("_Invoices", model);
}
Note you may want to make the CompanyID parameter nullable and adjust the query if your wanting to initially display unfiltered results
And a partial view _Invoices.cshtml
#model IEnumerable<yourAssembly.Invoice>
#foreach(var item in Model)
{
<tr>
<td>#Html.DisplayFor(m => item.InvoiceID)</td>
.... other table cells
</tr>
}
In the main view
#model yourAssembly.InvoiceViewModel
#Html.BeginForm()) // form may not be necessary if you don't have validation attributes
{
#Html.DropDownListFor(m => m.SelectedCompanyID, Model.Companies)
#Html.CheckboxFor(m => m.DisplayPaid)
<button id="filter" type="button">Filter results</button>
}
<table>
<thead>
....
</thead>
<tbody id="invoices">
// If you want to initially display some rows
#Html.Action("Invoices", new { CompanyID = someValue, DisplayPaid = someValue })
</tbody>
</table>
<script>
var url = '#Url.Action("Invoices")';
var table = $('#invoices');
$('#filter').click(function() {
var companyID = $('#SelectedCompanyID').val();
var isDisplayPaid = $('#DisplayPaid').is(':checked');
$.get(url, { CompanyID: companyID, DisplayPaid: isDisplayPaid }, function (html) {
table.append(html);
});
});
</script>
The alternative would be to post the form as your are, but rather than returning the view, use
return RedirectToAction("Invoice", new { companyID = model.SelectedCompanyID, DisplayPaid = model.DisplayPaid });
and modify the GET method to accept the additional parameter.
Side note: Your using the TagHelpers to generate
select asp-for="SelectedCompanyID" asp-items="Model.Companies" name="companyFilter" class="form-control"></select>
I'm not familiar enough with them to be certain, but if name="companyFilter" works (and overrides the default name which would be name="SelectedCompanyID"), then you generating a name attribute which does not match your model property and as a result SelectedCompanyID would be 0 (the default for int) in the POST method.
Appending ToList() to the statement that populates companies is converting the SelectList into a List<T>, which the form will not recognize as a SelectList. Also, by using the dynamic var keyword, you are masking this problem. Try this instead:
SelectList companies = new SelectList(await DbContext.Company.ToListAsync(), "CompanyID", "Name");
In general, try to avoid use of var unless the type is truly dynamic (unknown until runtime).
You put your model data out of form, so it would not submited!
<form asp-controller="Billing" asp-action="Index" method="post" class="form-horizontal" role="form">
<label for="companyFilter">Filter Company</label>
<select asp-for="SelectedCompanyID" asp-items="Model.Companies" name="companyFilter" class="form-control"></select>
<div class="checkbox">
<label>
<input type="checkbox" asp-for="DisplayPaid" />Display Paid
<input type="submit" value="Filter" class="btn btn-default" />
</label>
</div>
<br />
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().InvoiceID)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().CompanyID)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().Description)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().InvoiceDate)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().DueDate)
</th>
<th>
#Html.DisplayNameFor(model => model.Invoices.FirstOrDefault().Paid)
</th>
<th></th>
</tr>
#foreach (var item in Model.Invoices)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.InvoiceID)
</td>
<td>
#Html.DisplayFor(modelItem => item.CompanyID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.InvoiceDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.DueDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.Paid)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.InvoiceID }) |
#Html.ActionLink("Details", "Index", "InvoiceItem", new { id = item.InvoiceID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.InvoiceID })
</td>
</tr>
}
</table>
</form>
Using a for loop to create the with the companies will make it possible to map back and persist the company values
for(c = 0 ; c < Model.Companies.Count(); c++)
{
<input type='hidden' name='#Html.NameFor(Model.Companies[c].Propery1)' id='#Html.IdFor(Model.Comapnies[c].Propery1)' value='somevalue'>someText />
<input type='hidden' name='#Html.NameFor(Model.Companies[c].Propery2)' id='#Html.IdFor(Model.Comapnies[c].Propery2)' value='somevalue'>someText />
}
this ensures that the list is mapped back as the default model binder expects list to be in ListProperty[index] format

Use checkboxes in MVC view and pass checked state back to controller for processing

I have an IList of objects that I pass using a ViewModel to my view.
[HttpGet]
public ActionResult Reminder()
{
using(var db = new SalonContext())
{
var tomorrow = DateTime.Today.AddDays(1);
var homeSalon = GetHomeSalonId();
var myAppointments = db.Appointments.Include(c => c.Customer).Include(c => c.Treatment).Include(c => c.Stylist).Include(c => c.Salon).Where(c => c.SalonId == homeSalon).ToList();
var tomorrowsAppointments = myAppointments.Where(a => a.Start.Date == tomorrow).Select(rem => new FutureAppointment
{
AppointmentId = rem.AppointmentId,
Name = rem.Customer.Title + " " +rem.Customer.FirstName + " " + rem.Customer.Surname,
AppointmentTime = rem.Start,
Salon = rem.Salon.Name,
Stylist = rem.Stylist.FirstName,
Treatment = rem.Treatment.Name,
Email = rem.Customer.EMail,
SendReminder = true
}).ToList();
return View(tomorrowsAppointments);
}
}
The view renders the list with the check boxes using this code:
#model IList<MySalonOrganiser.Models.FutureAppointment>
#{
ViewBag.Title = "Email Reminders";
}
<h2>#ViewBag.Title</h2>
#using(Html.BeginForm("Reminder", "Appointment", FormMethod.Post))
{
#Html.AntiForgeryToken()
<table class="table">
<tr>
<th>
Send Reminder
</th>
<th>
Name
</th>
<th>
Appointment Time
</th>
<th>
Salon
</th>
<th>
Treatment
</th>
<th>
Stylist
</th>
<th>
EMail
</th>
<th></th>
</tr>
#for (var i =0; i < Model.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(x => x[i].SendReminder)
</td>
<td>
#Html.DisplayFor(x => x[i].Name)
</td>
<td>
#Html.DisplayFor(x => x[i].AppointmentTime)
</td>
<td>
#Html.DisplayFor(x => x[i].Salon)
</td>
<td>
#Html.DisplayFor(x => x[i].Treatment)
</td>
<td>
#Html.DisplayFor(x => x[i].Stylist)
</td>
<td>
#Html.DisplayFor(x => x[i].Email)
</td>
</tr>
}
</table>
<div>
<input type="Submit" value="Send Reminders" />
</div>
}
When I post the view back to the controller, I see the list's items of type FutureAppointment in the returned model, but everything except the checkbox state is null.
Can anyone help me with why and how to fix this so I see all the model data?
Jason.
It's because your form only contains input fields for the checkboxes. The DisplayFor helper will only create a text or label or whatever displaying the value of the other fields. You can fix this by adding hidden inputs for them:
#for (var i =0; i < Model.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(x => x[i].SendReminder)
</td>
<td>
#Html.DisplayFor(x => x[i].Name)
#Html.HiddenFor(x => x[i].Name)
</td>
<td>
#Html.DisplayFor(x => x[i].AppointmentTime)
#Html.HiddenFor(x => x[i].AppointmentTime)
</td>
<td>
#Html.DisplayFor(x => x[i].Salon)
#Html.HiddenFor(x => x[i].Salon)
</td>
<td>
#Html.DisplayFor(x => x[i].Treatment)
#Html.HiddenFor(x => x[i].Treatment)
</td>
<td>
#Html.DisplayFor(x => x[i].Stylist)
#Html.HiddenFor(x => x[i].Stylist)
</td>
<td>
#Html.DisplayFor(x => x[i].Email)
#Html.HiddenFor(x => x[i].Email)
</td>
</tr>
}
You are not posting back the values. Html.DisplayFor does not generate input tags, and only inputs inside a form are posted back to server. You will have to use Html.TexBoxFor or Html.HiddenFor methods for those properties.
Under each of the DisplayFor calls, add a #Html.HiddenFor(x=>x[i].Property) call to add a hidden field containing the same value.
The hidden field values will then get posted by the form and bound to your model when the data hits your controller.
The DisplayFor will only add a label to the output (unless overridden for the model with a display template), label values do not get posted by a form.

Pass ViewModel to ActionResult mvc4

I have a view that the model is IEnumerable. the view is the mvc basic view that is created upon creating new controller with read write actions.
I do not want that the edit action will call a different view, i want to add a button in the index view that by pressing the button in a specific row the button will call the action result with the model that was "pressed" and from there the logic will continue.
The View
#model IEnumerable<V1_ILotto.Areas.Admin.Models.WithdrawalModel>
#{
ViewBag.Title = "בקשות משיכה";
}
<h2>בקשות משיכה</h2>
#using (Html.BeginForm("Approve", "Withdrawals", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.User)
</th>
<th>
#Html.DisplayNameFor(model => model.WithdrawalAmount)
</th>
<th>
#Html.DisplayNameFor(model => model.Balance)
</th>
<th>
#Html.DisplayNameFor(model => model.IsApproved)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.User)
</td>
<td>
#Html.DisplayFor(modelItem => item.WithdrawalAmount)
</td>
<td>
#Html.DisplayFor(modelItem => item.Balance)
</td>
<td>
<p>
<input type="submit" value="Submit" />
</p>
</td>
</tr>
}
</table>
}
The Controller
[HttpPost]
public ActionResult Approve(WithdrawalModel withdrawalmodel)
{
if (ModelState.IsValid)
{
//logic for updating the db
return RedirectToAction("Index");
}
return View(withdrawalmodel);
}
Note : the view is in not getting a single model but IEnumerable of that model
only for helpers (except display) tie the data to the model. If you want data passed back you need to put your values in at least a hidden for
#Html.HiddenFor(x => x.Balance)
etc

Categories

Resources