Hidden value for each item in List (MVC) - c#

I'm trying to make some kind of a shopping cart, so I show a list of items in a view.
I added a submit button with the value "+ Add" and a number input with the value amount for each of the items displayed.
This is my code:
VIEW:
#model MyProject.ViewModel.AddProductsViewModel
#{
Layout = null;
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<table class="table" align="left" style="padding-left:15px">
<tr>
<th>
Product
</th>
</tr>
#foreach (var item in Model.ProductsList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Descripcion)
</td>
<td>
<input type="hidden" name="ProductId" value="#item.ProductId" />
<input type="number" id="Amount" name="Amount"
min="1" max="30" value="1">
<input type="submit" value="+ Add" class="btn btn-primary" />
</td>
</tr>
}
</table>
}
MODEL:
public class AddProductsViewModel
{
...stuff here...
public List<Products> ProductsList { get; set; }
}
I need my controller to get the productId from the item I clicked submit to, but this way I'm only getting the Id of the first item of the list.
How can I make that happen?
EDITED MY CODE FOR CONTEXT
EDIT
Thanks to everyone that responded. Your answers didn't solve the issue but I think I might have made the wrong question. I'll give a little bit of context: I'm trying to show a list of Products in a ViewModel where each has an Add to cart button and an input style number on it's side (By using a for each, as I made a List of Products, as shown in my code), and when I click the add to cart button it gives the info of that specific item to the controller.
I don't know if I was doing it wrong. Is this possible? If so, how?
The issue with my code is that no matter which add to cart button I clicked, it always gives the info of the first item on the list only.

Instead of using:
<input type="hidden" name="ProductId" value="#item.ProductId" />
Use:
#Html.HiddenFor(modelItem => item.ProductId)
Following code may help you:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Descripcion)
</td>
<td>
#Html.HiddenFor(modelItem => item.ProductId)
<input type="number" id="Amount" name="Amount"
min="1" max="30" value="1">
<input type="submit" value="+ Add" class="btn btn-primary" />
</td>
</tr>
}
If you are trying to send all data at once you can also use the following example:
#for( int i = 0; i < Model.Count(); ++i)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].Descripcion)
</td>
<td>
#Html.HiddenFor(modelItem => Model[i].ProductId)
<input type="number" id="Amount" name="Amount"
min="1" max="30" value="1">
<input type="submit" value="+ Add" class="btn btn-primary" />
</td>
</tr>
}
Where as you controller action method may look like:
[HttpPost]public ViewResult(ModelClass model)
{
...
}

Related

Razor Pages - Create unique identifier for controls in foreach loop

How can I create a unique name referance for a set of checkboxs when using foreach loop?
#foreach (var item in Model.myClass.Where(i => i.ID1 == 0))
{
<input type="text" asp-for="#item.Stuff" />
<input type="text" asp-for="#item.Stuff" />
<input type="text" asp-for="#item.Stuff" />
}
HTML (Output)
<input type="checkbox" checked="checked" id="item_Stuff" name="item_Stuff">
<input type="checkbox" checked="checked" id="item_Stuff" name="item_Stuff">
<input type="checkbox" checked="checked" id="item_Stuff" name="item_Stuff">
I have seen the Razor Pages recommendation here however this recommends using for loop or explicit indexing and this wont work in my case because I need the where clause on my statement.
EXAMPLE
#for (var i = 0; i < 5; i++)
{
<tr>
<td>
<input type="text" asp-for="Contacts[i].FirstName" />
</td>
<td>
<input type="text" asp-for="Contacts[i].LastName" />
</td>
<td>
<input type="text" asp-for="Contacts[i].Email" />
</td>
</tr>
}
Whenever you feel the urge to put counter variables in a content page or view, that's an indication that you are doing something sub-optimal. It's a code smell.
What I would do to minimise the code in the .cshtml file is filter the items in the PageModel. I would add another property:
public class MyPageModel : PageModel
{
public List<MyClass> Entities { get; set; } = new List<MyClass>();
public List<MyClass> EntitiesWithNoId => Entities.Where(e => e.ID == 0);
// blah blah
}
Then in the content page:
#for (var i = 0; i < Model.EntitiesWithNoId.Count(); i++)
{
<tr>
<td>
<input type="text" asp-for="EntitiesWithNoId[i].FirstName" />
</td>
<td>
<input type="text" asp-for="EntitiesWithNoId[i].LastName" />
</td>
<td>
<input type="text" asp-for="EntitiesWithNoId[i].Email" />
</td>
</tr>
}
That is an example of utilising the ViewModel aspect of the PageModel. Part of it's role is to act as a place to prepare data for the view.

asp.net core mvc form viewmodel empty on post

When I click the button to submit my form, the viewModel is not being bound. I'm not sure whether this is to do with the model property being an array.
Controller
[HttpPost]
public async Task<IActionResult> CommodityAdditionalCodes(CommodityAdditionalCodesViewModel viewModel)
{
//todo code in here which calls webservice
return View(viewModel);
}
ViewModel
public class CommodityAdditionalCodesViewModel
{
public CommodityAdditionalCodeType[] Types { get; set; }
}
View
#model Asm.Helios.ToolboxMvc.ViewModels.CommodityAdditionalCodesViewModel
#{
ViewData["Title"] = "CommodityAdditionalCodes";
}
<Section>
<button type="button" id="btnCommit">Commit Changes</button><span id="isDirtyMessage" class="warning" style="display:none">There are unsaved changes, press the commit changes button to save them</span>
</Section>
<form id="frmAdditionalCodes" name="frmAdditionalCodes" method="post" >
<table class="table header-fixed">
<thead>
<tr >
<th>Origin</th>
<th>Description</th>
<th>Valid From</th>
<th>Valid To</th>
<th>Type</th>
<th>Customs Identifier</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Types)
{
<tr>
<td>
<span>#item.Origin</span>
<input type="text" value="#item.Origin" style="display:none"/>
</td>
<td>
<span>#item.Description</span>
<input type="text" value="#item.Description" style="display:none" />
</td>
<td>
<span>#item.ValidFrom</span>
<input type="text" value="#item.ValidFrom" style="display:none" />
</td>
<td>
<span>#item.ValidTo</span>
<input type="text" value="#item.ValidTo" style="display:none" />
</td>
<td>
<span>#item.Type</span>
<input type="text" value="#item.Type" style="display:none" />
</td>
<td>
<span>#item.CustomsIdentifier</span>
<!--<input type="text" value="#item.CustomsIdentifier" style="display:none" />-->
#Html.TextBoxFor(m=>item.CustomsIdentifier, new {#style="display:none"})</td>
<td>
<button type="button" id="deleteItem">Delete</button>
<button type="button" id="editItem">Edit</button>
<button type="button" id="updateItem" style="display:none">Update</button>
<button type="button" id="cancelItem" style="display:none">Cancel</button>
</td>
</tr>
}
</tbody>
</table>
</form>
Don't use a foreach loop, instead use a classic for and index the model, for example Model.Types[index].
Also you should consider using tag helpers, it makes your Razor code much easier to read as it more closely resembles real HTML.
#for (var index = 0; index < Model.Types.Count; index++)
{
<input type="text" asp-for="Model.Types[index].Origin" />
<!-- etc... -->
}

Passing data from View to Controller using multiple POST methods? [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
I want to allow the user to check a box next to each item that they consider as "Good" and also allow them to delete the list of items with 2 separate button presses. How do I pass data to the corresponding controller actions?
I have an IEnumerable list of objects that contain a bool field.
Public class fruit
{
public string Name { get; set;}
public bool IsGood {get; set:}
}
I am displaying this in a table like so:
#model IEnumerable<Fruit>
<table>
<thead>
<tr>
<th>Good?</th>
<th>Name</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tbody>
<tr>
<td><input type="checkbox" class="checkbox" value="#item.IsGood" /></td>
<td>Item.Name</td>
</tr>
<tbody>
</table>
}
<input class="btn btn-primary" type="submit" name="Update" id="Update" value="Update" />
<input class="btn btn-danger" type="submit" name="Delete" id="Delete" value="Delete" />
How can this be done?
#model IEnumerable<Fruit>
<table class="tblFruitDetail">
<thead>
<tr>
<th>Good?</th>
<th>Name</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tbody>
<tr>
<td><input type="checkbox" class="checkbox" value="#item.IsGood" id="#item.FruitId"/></td>
<td>Item.Name</td>
</tr>
<tbody>
}
</table>
<input class="btn btn-primary" type="submit" name="Update" id="btnUpdate" value="Update"/>
<input class="btn btn-danger" type="submit" name="Delete" id="btnDelete" value="Delete"/>
var fruitIds = ",";
var checkboxes = document.querySelectorAll('.tblFruitDetail input[type="checkbox"]')
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function(e) {
fruitIds += e.target.id
})
});
$("#btnDelete").click( function(){
$.ajax({
type:'POST',
url:'/HomeController/DeleteFruitsById',
data: fruitIds,
dataType:'json',
success:function(result){
// do something on success..
},
error: function(err){
// handle error here..
}
});
});
HomeController.cs
[HttpPost]
public JsonResult DeleteFruitsById(string fruitIds)
{
// todo: code to delete fruit details..
// Split fruitIds and make it array of string
string[] fruits = fruitIds.Split(',');
.
.
.
return Json(result);
}
The name of the button you click will be posted back to the server. If you find Request["Update"] then the Update button was submitted. If you find Request["Delete"], the Delete button was clicked. You can then just decide whether to delete or update in your action method.
You can do something like
public ActionResult EditFruits(IEnumerable<Fruit> model)
{
if(this.Request.Form.AllKeys.Contains("Delete"))
DeleteSelectedFruits(model);
else if(this.Request.Form.AllKeys.Contains("Update"))
UpdateSelectedFruits(model);
return Wherever you want to go.
}

Asp.net Core MVC - on form posting to controller IEnumerable model in controller action is empty

I have a problem when I am trying to post IEnumerable from razor view to Controllor action method. Also result is the same if I use List.
I post my controllor action method also in comment. In my controllor action method I got list that is empty.
This is my View:
#model IEnumerable<Subject>
<form asp-action="AddNewSubjects" asp-controller="Teacher" method="post" role="form" class="form-horizontal">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Number of class</th>
<th>Level</th>
</tr>
</thead>
<tbody>
#if (Model != null)
{
var item = Model.ToList();
#for(int i=0;i<Model.Count();i++)
{
<tr>
<td>#item[i].ID</td>
<td>#item[i].Name</td>
<td>#item[i].ClassNumber</td>
<td>#item[i].Level</td>
</tr>
}
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-5">
<input type="submit" class="btn btn-primary" value="Save all subjects" />
</div>
</div>
</form>
This is my Controller:
private readonly ISubjectService _subjectService;
public TeacherController(ISubjectService subjectService)
{
_subjectService= subjectService;
}
[HttpPost]
public IActionResult AddNewSubjects(IEnumerable<Subject> subjects)
{
var newSubjects= (from p in subjects
where p.State== Status.New
select p);
var result = _subjectService.SaveTeacherSubjects(newSubjects);
return View("ProfesorPages");
}
I have no idea what you're trying to do here. Your form doesn't have any input element except the submit button. Of course you're not seeing anything posted back.
#model IEnumerable<Subject>
<form>
...
<tbody>
#for(int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
<input type="hidden" asp-for="Model[i].ID" />
</td>
<td>
<input type="text" asp-for="Model[i].Name" />
</td>
...
</tr>
}
</tbody>
...
</form>
Why??
Why did you convert your IEnumerable to a list named item? Why not just enumerate your subjects directly?
Why not create a different set of models called ViewModel and pass that to the View, instead of using your model from your database directly on the View?

MVC: Passing Value from View to Controller

I am trying to pass values from a view to a controller in MVC. I am using a ViewModel and normally the values would bind to the properties as long as the names are the same. However because the values are generated via a foreach loop the names of the values do not match the names of the properties in the view model.
I am working around this by assigning the values to a variable in Razor. However one of my values is in a text box on the form and the value is not being passed to the controller and I cannot work out why.
I get a null exception when clicking the button.
VIEW Code is below:
#model PagedList.IPagedList<Mojito.Domain.ViewModels.ShoppingCartProductItem>
#using System.Web.UI.WebControls
#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>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().Description)
</th>
<th>
#Html.ActionLink("Price", "Index", new { sortOrder = ViewBag.SortByPrice, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().Quantity)
</th>
<th>
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.TextBoxFor(modelItem => item.Quantity)
</td>
<td>
#{string Description = item.Description;}
#{decimal Price = item.Price;}
#{int Quantity = item.Quantity; }
#using (Html.BeginForm("AddToCart", "ShoppingCart", FormMethod.Post))
{
<div class="pull-right">
#if (Request.Url != null)
{
<input type="text" hidden="true" name="Description" value=#Description />
<input type="text" hidden="true" name="Price" value=#Price />
<input type="text" hidden="true" name="Quantity" value=#Quantity />
#Html.Hidden("returnUrl", Request.Url.PathAndQuery)
<input type="submit" class="btn btn-success" value="Add to cart" />
}
</div>
}
</td>
</tr>
}
</table>
<div class="col-md-12">
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount
</div>
#Html.PagedListPager(Model, page => Url.Action("Index",
new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
Controller Code below
public ActionResult AddToCart(Cart cart, MojitoProduct product, string returnUrl, int Quantity =1)
{
if (product != null)
{
cart.AddItem(product, Quantity);
}
return RedirectToAction("Index", new { returnUrl });
}
Do not use foreach. Use a for-loop instead and within this, qualify the full path to your properties using the index.
Better yet: use a Edit- or DisplayTemplate for the ShoppingCartProductItem. This will also keep your path.
You have to use for loop instead of foreach:
#for (int i=0;i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].Description)
</td>
<td>
#Html.DisplayFor(modelItem => Model[i].Price)
</td>
<td>
#Html.TextBoxFor(modelItem => Model[i].Quantity)
</td>
..........................
..........................
..........................
}
you can also post all using one form by posting List<ShoppingCartProductItem>, see Model Binding To A List
Your textboxes so values out of the form.
Try like below
#using (Html.BeginForm("AddToCart", "ShoppingCart", FormMethod.Post))
{
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.TextBoxFor(modelItem => item.Quantity)
</td>
<td>
#{string Description = item.Description;}
#{decimal Price = item.Price;}
#{int Quantity = item.Quantity; }
<div class="pull-right">
#if (Request.Url != null)
{
<input type="text" hidden="true" name="Description" value=#Description />
<input type="text" hidden="true" name="Price" value=#Price />
<input type="text" hidden="true" name="Quantity" value=#Quantity />
#Html.Hidden("returnUrl", Request.Url.PathAndQuery)
<input type="submit" class="btn btn-success" value="Add to cart" />
}
</div>
</td>
</tr>
}
}
I resolved this in the short term by using new and forcing the name of the parameter.
#Html.HiddenFor(modelItem => t.NoOfUsers, new { Name = "NoOfUsers", id = "NoOfUsers" })

Categories

Resources