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.
}
Related
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)
{
...
}
On my view, I have a table with a form that I want to use to delete a particular row. I use a foreach loop to generate an hidden input field with the row value that I want to pass to the controller and asp-for tag for model biding, and a submit button.
The value that is passed to the controller is always the first row. I'm inclined to think that the reason for this behavior is that the generated input fields all have the same name attribute, because the asp-for expression is invariant for every iteration of the foreach loop.
Is there a straight-forward way to implement this using a form and a POST request, or should I just use anchors with route values, i.e., GET requests?
Here's my ViewModel:
public class RolesViewModel
}
public IList<AppUser> UsersInRole {get; set;}
public string SelectedRole {get; set;}
public RemoveUserFromRole RemoveUser {get; set;}
public class RemoveUserFromRole
{
public string UserName {get; set;}
public string RoleName {get; set;}
}
}
My View
<form method="post" asp-action="RemoveUser" id="removeUserForm"></form>
<table id="userTable" class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">User name</th>
<th scope="col" class="text-center">Delete</th>
</tr>
</thead>
<tbody>
#foreach (var user in Model.UsersInRole)
{
<tr>
<td>#user.UserName</td>
<td class="text-center">
<input form="removeUserForm" asp-for="RemoveUser.UserName" type="hidden" value="#user.UserName" />
<input form="removeUserForm" asp-for="RemoveUser.RoleName" type="hidden" value="#Model.SelectedRoleName" />
<button form="removeUserForm" type="submit" class="btn btn-sm btn-link text-danger py-0 my-0">
<i class="fas fa-times"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
And my action method in controller
[HttpPost]
public async Task<IActionResult> RemoveUser(RolesViewModel model)
{
//model.RemoveUser.UserName always have the value from the first row
var user = await _userManager.FindByNameAsync(model.RemoveUser.UserName);
if (user == null)
return RolesError(await GetModel());
var result = await _userManager.RemoveFromRoleAsync(user, model.RemoveUser.RoleName);
if (!result.Succeeded)
return RolesError(await GetModel());
return RedirectToAction("Roles", new { roleName = model.RemoveUser.RoleName });
}
Thanks in advance for your time.
According to your codes, I found you have multiple hidden filed which contains the user.UserName.
If you click the submit button, it will upload all the hidden filed value to the code-behind and it will just bind the first one, this is the reason why your model is always first one.
You could find the formdata in F12 developtool's network.
To solve this issue, we have a easily but not a good solution.
We could set mutiple form tag in your table to avoid post all the all the hidden filed username value to controller:
Like below:
<table id="userTable" class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">User name</th>
<th scope="col" class="text-center">Delete</th>
</tr>
</thead>
<tbody>
#foreach (var user in Model.UsersInRole)
{ int i = 0;
<tr>
<td>#user.UserName</td>
<td class="text-center">
<form method="post" asp-action="RemoveUser" id="#user.UserName">
<input form="#user.UserName" name="RemoveUser.UserName" type="hidden" value="#user.UserName" />
<input form="#user.UserName" name="RemoveUser.RoleName" type="hidden" value="#Model.SelectedRole" />
<button form="#user.UserName" type="submit" class="btn btn-sm btn-link text-danger py-0 my-0">
<i class="fas fa-times">iiiii</i>
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
If you choose this way, you should rebuild all your view's html makeup.
Besides, you could try to use ajax to achieve your requirement, this solution is better than before solution. You could use jquery to get the right form data according to the submit button's id or position and then use jquery ajax to post the form data into controller. Then you could return the redirect url instead of RedirectToAction methods.
More details about how to use ajax to send form data, you could refer to below codes:
#model MVCRelatedIssue.Models.RolesViewModel
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<form method="post" asp-action="RemoveUser" id="removeUserForm">
<table id="userTable" class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">User name</th>
<th scope="col" class="text-center">Delete</th>
</tr>
</thead>
<tbody>
#foreach (var user in Model.UsersInRole)
{
<tr>
<td>#user.UserName</td>
<td class="text-center">
<input form="removeUserForm" name="RemoveUser.UserName" type="hidden" value="#user.UserName" />
<input form="removeUserForm" name="RemoveUser.RoleName" type="hidden" value="#Model.SelectedRole" />
<button form="removeUserForm" type="submit" id="submit" class="btn btn-sm btn-link text-danger py-0 my-0 subbtn">
<i class="fas fa-times">iiiii</i>
</button>
</td>
</tr>
}
</tbody>
</table>
</form>
#section Scripts{
<script>
$(document).ready(function () {
$(".subbtn").bind("click", function (e) {
e.preventDefault();
var formdata = new FormData();
var UserName = $(this).prev().prev().val();
formdata.append("RemoveUser.UserName", UserName);
console.log(UserName);
var roleName = $(this).prev().val();
formdata.append("RemoveUser.RoleName", roleName);
console.log(roleName);
$.ajax({
type: "POST",
url: "/RemoveUser/RemoveUser",
data: formdata,
contentType: false,
processData: false,
success: function (data) {
alert("success");
window.location.href = data;
}
});
});
});
</script>
}
Controller:
[HttpPost]
public async Task<IActionResult> RemoveUser(RolesViewModel model)
{
//model.RemoveUser.UserName always have the value from the first row
//var user = await _userManager.FindByNameAsync(model.RemoveUser.UserName);
//if (user == null)
// return RolesError(await GetModel());
//var result = await _userManager.RemoveFromRoleAsync(user, model.RemoveUser.RoleName);
//if (!result.Succeeded)
// return RolesError(await GetModel());
string redirecturl = "/RemoveUser/Roles?roleName=" + model.RemoveUser.RoleName;
return Ok(redirecturl);
}
Result:
You can use a Delete Link
in View
#foreach (var user in Model.UsersInRole)
{
<a href="#Url.Action("RemoveUser", "YOUR_Controller",new {username = user.UserName})"
onclick="return confirm('Do You want to Delete');"
</a>
}
In Controller
[HttpGet]
public async Task<IActionResult> RemoveUser(String username)
{
// Get the user Object the delete it
}
I was capable of solving this issue with minimal code footprint.
As it turns out, <td> tags can have forms, so, knowing that, it is possible to have a different form on each row, like so:
View:
//Remove inline table form
<table id="userTable" class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">User name</th>
<th scope="col" class="text-center">Delete</th>
</tr>
</thead>
<tbody>
#foreach (var user in Model.UsersInRole)
{
<tr>
<td>#user.UserName</td>
<td class="text-center">
//now each form will have the correct, row-wise formdata
<form method="post" asp-action="RemoveUser">
<input asp-for="RemoveUser.UserName" type="hidden" value="#user.UserName" />
<input asp-for="RemoveUser.RoleName" type="hidden" value="#Model.SelectedRoleName" />
<button type="submit" class="btn btn-sm btn-link text-danger py-0 my-0">
<i class="fas fa-times"></i>
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
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?
I have been trying to select multiple rows from my table of data(Generated using EF) and then pass all selected rows to the next view to perform some action. On passing the data to the next view i am getting the following error :
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Temporary local of type 'int[]'> was null.
Any help on how to solve this will be appreciated.
Below is my code:
View:
<div class="row">
<div class="col-md-12">
<!-- Advanced Tables -->
<div class="panel panel-default">
<div class="panel-heading">
#using (Html.BeginForm()) {
<form action="#" method="post">
<label>Search by Company Name:</label> #Html.TextBox("SearchString")
<input type="submit" value="Go" placeholder="Search" style="background-color: #0a9dbd; color: white; border-color: #0a9dbd;">
<label>Search by Card Number:</label> #Html.TextBox("searchCard")
<input type="submit" value="Go" placeholder="Search" style="background-color: #0a9dbd; color: white; border-color: #0a9dbd;">
Export to Excel
</form>
}
</div>
<div class="panel-body">
Add Gift Card
Get Card Balance
Load Cards
<br />
<br />
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover" id="dataTables-example">
<thead>
<tr>
<th>Card ID</th>
<th>Company</th>
<th>Card Number</th>
<th>Card Number 2</th>
<th>Date Created</th>
<th>Card Status</th>
<th>Discount Level ID</th>
<th>Loyalty Level ID</th>
<th>Gift Card Enabled</th>
<th>Loyalty Enabled</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td><input type="checkbox" name="ids" value="#item.CardID" /></td>
<td>#item.CardID</td>
<td>#item.Customer.CustomerCompanyName</td>
<td>#item.CardNumber</td>
<td>#item.CardNumber2</td>
<td>#item.CardDate</td>
<td>#item.CardStatus</td>
<td>#item.DiscountLevelID</td>
<td>#item.LoyaltyLevelID</td>
<td>#item.GiftCardEnabled</td>
<td>#item.LoyaltyEnabled</td>
<td>
<i class="fa fa-edit "></i> Edit <br />
</td>
</tr>
}
</tbody>
</table>
Page #(Model.PageCount
< Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount #Html.PagedListPager(Model, page=> Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
</div>
</div>
</div>
<!--End Advanced Tables -->
</div>
</div>
Controller:
public ActionResult PostCards(int[]ids)
{
var myObject = new Card();
foreach(var id in ids)
{
myObject = db.Cards.Single(o => o.CardID == id);
return RedirectToAction("LoadCards", myObject);
}
return View();
}
public ActionResult LoadCards()
{
return View();
}
I need the selected data to be passed to the LoadCards view.
Let us first look at the NullReference you are getting. The problem here is that no correct index is created to bind the checkboxes to an array. Use a for loop instead of foreach. In MVC/Razor, how do I get the values of multiple checkboxes and pass them all to the controller?
To get the desired behaviour:
change the foreach to a for loop so the correct indices for sending the data will be created.
add a checkbox in each row that lets the user select the rows to submit.
your action should recieve a collection of models for each row. This model always transports the CardId and tells us whether it was selected.
public class SelectedCardModel {
public int CardId { get; set; }
public bool IsSelected {get; set;}
}
In the view:
#using (Html.BeginForm("PostCards", "CustomerView", FormMethod.Post) {
// <table> etc ...
<tbody>
#for (var i = 0; i < Model.Count; i++) {
#{ var item = Model.ElementAt(i); }
<tr>
<td>
#Html.Hidden("CardId[" + i + "]")
#Html.CheckBox("IsSelected[" + i + "]")
</td>
// display other properties of item ...
<td>#item.CardID</td>
// ...
</tr>
}
</tbody>
</table>
<button type="submit">Load Cards</button>
}
Action:
[HttpPost]
public ActionResult PostCards(SelectedCardModel[] selectedCards) {
foreach(var card in selectedCards) {
if (card.IsSelected) {
var selectedId = card.CardId;
// ...
}
}
}
I have this code in C# using EF Core 1.2 where I have a form containing two submit buttons. The first button 'upload' invokes my method 'UpLoadMyFile' which compares
each line from my textarea to a pattern and returns a string which tells me if it matches one pattern.
Then I add all my text and its states into a
List <Tuple<string, string>>
that I pass to my View via a ViewModel and display each line plus its state in a table.
Now I'm trying to save each line into my database when I click my second button 'save'. But every time I try to save my lines a NullReferenceException occurs
which tells me my List from my table is null.
I would like to know how to pass all lines and states from my Table 'MyTupleList' to my Post Method since I really don't know how to fix my problem.
My View:
#model Models.ViewModels.CreateSaetzeModelView
<form asp-action="ProcessCreateLines" asp-controller="SoftwareService" method="post" enctype="multipart/form-data">
<div class="div-marginBottom">
<table class="table">
<tbody>
<tr>
<td>
<textarea name="ExpressionTextarea" id="ExpressionTextarea" runat="server" TextMode="MultiLine" asp-for="#Model.LoadExpressions"></textarea>
<div class="col-md-10">
<input type="submit" name="upload" value="upload" /> !--Calls 'uploadmyfile' action-->
</div>
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<br />
<div class="div-ExpressionEingabe">
</div>
#if (Model.MyLinesList != null)
{
<div class="div-marginBottom">
<table id="MyTupleList" class="table table_align">
<thead>
<tr>
<th>
State
</th>
<th>
MyLines
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.MyLinesList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Item2)
</td>
<td>
#Html.DisplayFor(modelItem => item.Item1)
</tr>
}
</tbody>
</table>
<input type="submit" name="save" value="save" />
</div>
}
My Code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ProcessCreateLines(string upload, string save, CreateLinesModelView cmv)
{
//Button 'upload': Checking my lines
if (!string.IsNullOrEmpty(upload))
{
string expressions = Request.Form["ExpressionTextarea"].ToString();
List<Tuple<string, string>> result = new FileUploadController().CheckExpressions(expressions);
cmv.MyLinesList = result;
return View("ProcessCreateLines", cmv);
}
//Button 'save': Saving my lines into a Database
if (!string.IsNullOrEmpty(save))
{
// ****************************MyLinesList is null*******************
var list = cmv.MyLinesList;
...saving my list into database...
}
}
I managed to solve my problem thanks to the comment of #StephenMuecke by using instead of a Tupel a selfmade class
public class MyListModel
{
public string myLine { get; set; }
public string myState { get; set; }
}
}
and creating a List out of it in my ViewModel
public List<MyListModel> LineWithState { get; set; }
Also in my View I replaced the foreach loop with a for loop
#for (int i = 0; i < Model.LineWithState.Count; i++)
{
<tr>
<td>
#Html.TextBoxFor(m=>m.LineWithState[i].myState)
</td>
<td>
#Html.TextBoxFor(m=>m.LineWithState[i].myLine)
</tr>
}