I have a PersonViewModel that consists of several List<SubViewModel> (Name, Address, etc.)
I have an Html Form with tables for each SubViewModel, where the user can update,add,delete and set items to the primary entry for the person.
a simplified table for names:
#foreach (PersonNameViewModel name in Model.Names)
{
<tr>
<td>
#Html.DisplayFor(modelItem => name.FullName)
</td>
<td>
#Html.DisplayFor(modelItem => name.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => name.MiddleName)
</td>
<td>
#Html.DisplayFor(modelItem => name.LastName)
</td>
<td class="center">
#Html.RadioButtonFor(modelItem => name.IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(modelItem => name.ToDelete)
</td>
</tr>
}
From here, I don't know how to get all the data back into the PersonViewModel when the user submits the form, to update the person record. If I simply accept a PersonViewModel in my POST controller, it's completely empty. I looked at FormCollection, but I'm not sure if that's right for this. Can someone with experience point me in the right direction for building my model back up for updating? Thankyou very much.
Everyone's answer is correct, But if you want to update people's information, you need to use Html Helper Editor NOT Display. Also, you still can use foreach in your view, just change it like below:
#foreach (var name in Model.Names.Select((value, i) => new { i, value }))
{
<tr>
<td>
#Html.EditorFor(m => m.Names[#name.i].FullName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].FirstName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].MiddleName)
</td>
<td>
#Html.EditorFor(m => m.Names[#name.i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => m.Names[#name.i].IsPrimary,"")
</td>
<td class="center">
#Html.CheckBoxFor(m => m.Names[#name.i].ToDelete)
</td>
</tr>
}
You should use sth like this (good article about model binding of collections and complex data here):
#for(var i = 0;i < Model.Names.Count;i++)
{
<tr>
<td>
#Html.DisplayFor(m => Model.Names[i].FullName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].MiddleName)
</td>
<td>
#Html.DisplayFor(m => Model.Names[i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => Model.Names[i].IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(m => Model.Names[i].ToDelete)
</td>
</tr>
}
Try replacing the foreach with a for loop:
#for (int i = 0; i < Model.Names.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.Names[i].FullName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].MiddleName)
</td>
<td>
#Html.DisplayFor(m => m.Names[i].LastName)
</td>
<td class="center">
#Html.RadioButtonFor(m => m.Names[i].IsPrimary, "")
</td>
<td class="center">
#Html.CheckBoxFor(m => m.Names[i].ToDelete)
</td>
</tr>
}
The expression parsing/model binding does a better job with indexes than with foreach temp variables.
Related
I have one table which is for contracts , And I have link beside each contract , when I click to the link they transfer to other page which is contract month but i want to display the data only for that contract which I click not all data.
Contact View :
#using (Html.BeginForm())
{
#Html.ActionLink("Create", "Create")
<table class="table table-bordered f" cellspacing="0">
<thead>
<tr>
</tr>
</thead>
#foreach (var item in Model)
{
<tbody>
<tr>
<td>
#Html.DisplayFor(modelItem => item.Contract_Num)
</td>
<td>
#Html.DisplayFor(modelItem => item.Contract_Start)
</td>
<td>
#Html.DisplayFor(modelItem => item.Contract_End)
</td>
<td>
#Html.DisplayFor(modelItem => item.Status)
</td>
<td>
#Html.DisplayFor(modelItem => item.typeOfRent.TypeOfRent_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.AmountOfRent)
</td>
<td>
#Html.DisplayFor(modelItem => item.Total)
</td>
<td>
#Html.DisplayFor(modelItem => item.customers.Customer_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.sections.Section_Name)
</td>
<td>
#Html.ActionLink("Contract Month", "Index", "ContractMonth")
</td>
</tbody>
}
</table>
contract month view :
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.contracts.Contract_Num)
</td>
<td>
#Html.DisplayFor(modelItem => item.Monthe)
</td>
<td>
#Html.DisplayFor(modelItem => item.contracts.AmountOfRent)
</td>
<td>
#Html.DisplayFor(modelItem => item.Receipt)
</td>
<td>
#{
var Balance = item.contracts.AmountOfRent - item.Receipt;
}
#Balance
</td>
Contract month controller:
public ActionResult Index()
{
var ContractMonth =_context.ContractMonth.Include(s => s.contracts).ToList();
return View(ContractMonth);
}
First of all change your Index action method inside ContractMonthController to get specific records with respect to ContractId
public ActionResult Index(int id)
{
var contractMonth = _context.ContractMonth.Where(c => c.ContractsId == id).Include(s => s.contracts).ToList();
return View(contractMonth);
}
And then change your action to accept id as query string parameter,
<td>
#Html.ActionLink("Contract Month", "Index", "ContractMonth", new { id = item.Contracts_Id }, null)
</td>
You can add the primary key (e.g. Id) of each contract to the url of each link as a query string. Then when getting the contract month data you can use the pk parameter to filter the data, for example (in the contract month controller):
public ActionResult Index(int contractId)
{
var ContractMonth =_context.ContractMonth.Where(c => c.ContractId == contractId).Include(s => s.contracts).ToList();
return View(ContractMonth);
}
I'm using default schema of Create View and in my model i've got a field that's a DateTime.
Now i'm doing that, cause if i use the #Html.DisplayFor(modelItem => item.BirthDate) i see a string formatted in this way 13/02/1989 00:00:00 instead of this 16/02/1989
First of All: How to make all with DisplayFor statement? And... How it Works with datatype, it's a good practice?
Next: I wonder i have some homework to do, some suggestion?
#foreach (var item in Model)
{
<tr class="odd gradeX">
<td>
<input name="UserIDs" type="checkbox" class="checkboxes" value=#item.Id />
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName) #Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.Phone)
</td>
<td>
#Html.DisplayFor(modelItem => item.Fax)
</td>
<td>
#item.BirthDate.ToShortDateString()
</td>
<td>
#Html.ActionLink("Details", "Details", new { Id = item.Id })
</td>
</tr>
}
Add this using statement to your model:
using System.ComponentModel.DataAnnotations;
Then add this attribute to your BirthDate field:
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy'-'MM'-'dd}")]
Edit as necessary.
I have a list of Topic's. Each Topic have a list of Message. When i click on "Details" I want to open a new View that display a list of the Message's from the Topic.
I expect that I need to modify this line: (this is what I have so far)
#Html.ActionLink("Details", "Details", new { id=item.Id }, null)
The Message's are accessed by item.Message.
And the first line in the Detail View
#model IEnumerable<Models.TopicMessage>
This is my Topic View:
#model IEnumerable<Models.Topic>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.ActionLink("Details", "Details", new { id=item.Id }, null)
</td>
</tr>
}
</table>
There are a number of ways to do that. You can use the Action method to execute the controller action directly:
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.Action("Details", "Details", new { id=item.Id })
</td>
</tr>
Or you can bypass the controller and render the view directly using this overload of DisplayFor:
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Messages, "MessagesDetail")
</td>
</tr>
Or if you define the template as the default DisplayTemplate, you can just do this:
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Messages)
</td>
</tr>
I fixed this by making a somethingViewModel that contained both the liste and the Id. That way i could contain the two information from two difference places in one new object I had access to in the View.
I got the following Razor view that display a list of HDD so an user and add to a cart. When the user press the button next to each row of HDD, it will pass the quantity and HDD's identity to a controller. However, while each HDD's ID does display properly, "hddId" is always 1 and "quantity" is correctly 5 when I inspect the Controller's parameters.
#model IEnumerable<TestStore.Models.Hdd>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
#using(Html.BeginForm("AddToCart","Cart")){
<table>
<tr>
<th>
Ident
</th>
<th>
Brand
</th>
<th>
Name
</th>
<th>
Model
</th>
<th>
Speed
</th>
<th>
Capacity
</th>
<th>
Cache
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.hddId)
</td>
<td>
#Html.Hidden("hddId", item.hddId)
#Html.Hidden("quantity", 5)
#Html.DisplayFor(modelItem => item.brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.model)
</td>
<td>
#Html.DisplayFor(modelItem => item.speed)
</td>
<td>
#Html.DisplayFor(modelItem => item.capacity)
</td>
<td>
#Html.DisplayFor(modelItem => item.cache)
</td>
<td>
<input type="submit" value="Add to Cart" />
</td>
</tr>
}
</table>
}
Codingbiz is correct. The submit button wasn't referring to any particular row but the entire table. I suspect the reason why "hddId" was always "1" is because that's the first reference of "hddId" it see when the form is submitted. I changed it so #using(Html.BeginForm(...)) is inside the #foreach loop so each row has its own form and submit button:
#foreach (var item in Model) {
<tr>
#using(Html.BeginForm("AddToCart","Cart")){
<td>
#Html.DisplayFor(modelItem => item.hddId)
</td>
<td>
#Html.Hidden("hddId", item.hddId)
#Html.Hidden("quantity", 5)
#Html.DisplayFor(modelItem => item.brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.model)
</td>
<td>
#Html.DisplayFor(modelItem => item.speed)
</td>
<td>
#Html.DisplayFor(modelItem => item.capacity)
</td>
<td>
#Html.DisplayFor(modelItem => item.cache)
</td>
<td>
<input type="submit" value="Add to Cart" />
</td>
}
</tr>
}
As others have said, this doesn't work because when you submit, you're submitting every row in the table, not just the one clicked.
One option is to do what they suggest in this article:
http://federmanscripts.com/2010/01/12/form-and-table-row-nesting-workaround/
That is, using javascript, you copy the form values on button press to a hidden row and submit that.
I got around the validation problem by using button type="submit" instead of input type="submit". It still works correctly by passing the id number to the controller.
#using(Html.BeginForm("AddToCart","Cart")){
<table>
<tr>
//header stuff here
</tr>
#foreach (var item in Model) {
//row data goes here
//remove hidden field reference to #item.hddId
<tr>
<td>
<button type="submit" value="#item.hddId" name="hddId">Add to Cart</button>
</td>
</tr>
</table>
}
I'm returning quite a few rows on a view page. I'm wanting to show several tables, each being a subset of the full results. I have a foreach loop, and I'm wondering how I filter the results so "ResponseCode > 400" and "ExtFlag = 1"
#foreach (var res in Model.LinkCheckerResults)<br>
{
<tr>
<td>
#Html.DisplayFor(model => res.SourceURL)<br>
</td>
<td>
#Html.DisplayFor(model => res.TargetURL)
</td>
<td>
#Html.DisplayFor(model => res.Response)
</td>
</tr>
}
Many Thanks.
Assuming ResponseCode and ExtFlag are members of whatever LinkCheckerResults returns, you can do this as you normally would in a foreach loop in C#.
With LINQ:
#foreach (var res in Model.LinkCheckerResults
.Where(lcr => lcr.ResponseCode > 400 && lcr.ExtFlag == 1))
{
<tr>
<td>
#Html.DisplayFor(model => res.SourceURL)<br>
</td>
<td>
#Html.DisplayFor(model => res.TargetURL)
</td>
<td>
#Html.DisplayFor(model => res.Response)
</td>
</tr>
}
Or an if statement:
#foreach (var res in Model.LinkCheckerResults)<br>
{
if (res.ResponseCode > 400 && res.ExtFlag == 1)
{
<tr>
<td>
#Html.DisplayFor(model => res.SourceURL)<br>
</td>
<td>
#Html.DisplayFor(model => res.TargetURL)
</td>
<td>
#Html.DisplayFor(model => res.Response)
</td>
</tr>
}
}
Or (the true MVC way) provide the collection pre-filtered as a ViewData/ViewBag property.
you can either filter your model before is returned to the view or do :
#foreach (var res in Model.LinkCheckerResults.Where(x=>x.Response > 400 && x.ExtFlag = 1).ToList())<br>
{
<tr>
<td>
#Html.DisplayFor(model => res.SourceURL)<br>
</td>
<td>
#Html.DisplayFor(model => res.TargetURL)
</td>
<td>
#Html.DisplayFor(model => res.Response)
</td>
</tr>
}