How to model bind from view to controller via nested view - c#

i cant seem to pass the employee id value from a check box in the Employees.cshtml view to the Delete Post action method. The delete action method returns Delete.cshtml view which renders the Employees.cshtml as that is in an EditorTemplates folder under the Shared folder. When I click submit i cannot seem to pass the IEnumerable of checked #Model.Ids.
What I want to do is delete every entry that has been checked where the checked value is derived in the Employees.cshtml.
Obviously the issue is that the model binding (I think) will be done from the button submit from the Delete.cshtml page, how can I change this?
The Delete action method.
[HttpGet]
public ActionResult Delete()
{
return View(db2.Employees.ToList());
}
The Delete Post action method.
[HttpPost]
public ActionResult Delete(IEnumerable<int> EmployeeIDToDelete)
{
if (EmployeeIDToDelete != null)
{
var employeesToDelete = db2.Employees.Where(x => EmployeeIDToDelete.Contains(x.Id)).ToList();
foreach (var item in employeesToDelete)
{
db2.Employees.Remove(item);
}
db2.SaveChanges();
RedirectToAction("Delete");
}
return View(db2.Employees.ToList());
}
My Delete view
#model IEnumerable<MVC_Example2___ADO.Models.Employees>
#{
ViewBag.Title = "Delete";
}
<html>
<body>
#using (Html.BeginForm())
{
<table align="center">
<thead>
<tr>
<td>Check</td>
<td>Photo</td>
<td>Name</td>
<td>Gender</td>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
</table>
<input type="submit" name="submit" value="Delete Entries" />
}
</body>
</html>
My Employees View
#model MVC_Example2___ADO.Models.Employees
<tr>
<td><input type="checkbox" name="employeeIdsToDelete" id="employeeIdsToDelete" value="#Model.Id" /></td>
<td>#Html.Image(#Model.Photo, #Model.AlternateText, 125, 130)</td>
<td>#Model.FullName</td>
<td>#Model.Gender</td>
</tr>

You have named your checkboxes name="employeeIdsToDelete" but the parameter of your POST method is IEnumerable<int> EmployeeIDToDelete (not plural). The names must match.
Note also you should remove id="employeeIdsToDelete" from the input since this is generating duplicate id attributes which is invalid html.

Related

ASP.NET MVC - Http 404 Error when calling HttpPost Method in Controller

I was looking around and trying to find a solution for my problem, but somehow I can not find the right answer.
I have an Index.cshtml view that looks like this:
#model IEnumerable<MyProject.Models.Conditions>
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
<style> ... </style>
<div class="container1">
<div class="box1">
<div class="box-cell1 box1">
<fieldset id="field1">
<legend>CustomerNumber</legend>
#Html.TextBox("s_custNum")
<input type="submit" value=">" style="height: 22px; width: 50px; padding:0px;" />
</fieldset>
</div>
</div>
</div>
<table class="tableMain" id="x">
<tr class="trMain">
<th class="thMain">
#Html.DisplayNameFor(model => model.ID)
</th>
<th class="thMain">
#Html.DisplayNameFor(model => model.NAME)
</th>
<th class="thMain">
#Html.ActionLink("Edit Lines", "EditAll", "EditAll", null)
</th>
</tr>
#foreach (var item in Model)
{
<tr class="trMain">
<td class="tdMain">
#Html.DisplayFor(modelItem => item.ID)
</td>
<td class="tdMain">
#Html.DisplayFor(modelItem => item.NAME)
</td>
<td class="tdMain">
<input type="checkbox" class="chkCheckBoxId" name="custId" value="#item.ID" />
</td>
</tr>
}
</table>
I'm currently listing all records as table on my site and each record has a checkbox. The header has an action link for redirecting the user to a new editing site. At the top of the site is a search Textbox.
The method in my controller looks like this:
[HttpPost]
public ActionResult EditAll(FormCollection collection)
{
var test = Request.Form.GetValues("custId");
string[] ids = collection["custId"].Split(new char[] { ',' });
return View();
}
Here I'm trying to get all the ids from the records that are checked. However, I get an error 404 that the page can not be found. When I remove the [HttpPost] attribute, the method gets called but returns no values in "test" or "ids".
I even tried to add values to my Html.BeginForm, but then the searchbar on the site does not work anymore.
Could someone help me retrieving the ids of the checked records? I'm new to ASP.NET MVC.
Many thanks!
The action link you created creates an anchor tag (https://www.w3schools.com/tags/tag_a.asp)
#Html.ActionLink("Edit Lines", "EditAll", "EditAll", null)
Anchor tags are a GETs, not POSTs.
What you are looking for since you already have a BeginForm() and an input button -- which would cause your submit button to post back to the "Index" action -- is maybe doing this in Javascript or putting multiple forms on the page.
Forms cannot be nested inside of each other, but you can have multiple on a page.
Alternatively, just change it to an [HttpGet] if that's what you are trying to do.
Edit:
Javascript example
How to send data in jquery.post to mvc controller which use ViewModel as parameter?
var myData = {
Parameter1: $("#someElementId").val(),
Parameter2: $("#anotherElementId").val(),
ListParameter: { /* Define IEnumerable collections as json array as well */}
// more params here
}
$.ajax({
url: 'someUrl',
type: 'POST',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(myData)
});
[HttpPost]
public JsonResult Create(CustomViewModel vm)
{
// You can access your ViewModel like a non-ajax call here.
var passedValue = vm.Parameter1;
}

Value gets nulled on refresh of page after clicking <a> tag

I'm having a problem with a property that is getting nulled when an user clicks on a link that refreshes the same page, below the code:
Expenditures.cshtml
#page
#model WorkInProgress.Pages.Expenditures
#{
ViewData["Title"] = "Expenditures";
}
<h1>Project Information</h1>
<form method="post">
<table class="table table-hover">
<thead class = "table-light">
<tr>
<th></th>
<th class = text-end>Billable</th>
<th class = text-end>Unbilled</th>
<th class = text-end>Billing Hold</th>
</tr>
</thead>
<tbody>
#Html.HiddenFor(model => model.PersonnelNo)
<tr>
<td>Labor</td>
<td>#Model.PersonnelNo</td>
<td class=text-end>
<a asp-page="" asp-route-ExpenditureFilter="UnbilledLabor" asp-route-projectId=#Model.Project.ProjectId>
#Model.PersonnelNo
</a>
</td>
</tr>
</table>
<div class="form-control">
<input type="submit" value="Save" class="btn btn-primary"/>
</div>
</form>
Expenditures.cshtml.cs
public class Expenditures : PageModel
{
[BindProperty(SupportsGet = true)] public string PersonnelNo { get; set; }
public async Task OnGetAsync(int projectId, string ExpenditureFilter, string personnelNo)
{
PersonnelNo = personnelNo;
}
public async Task<IActionResult> OnPostAsync()
{
return Page();
}
}
the issue I'm having is the following:
When the page first loads, the value of PersonnelNo is populated.
When the user clicks "save" the value of PersonnelNo is still there, the page "refreshes" and the value is populated again
BUT, when the user clicks on the PersonnelNo cell (declared in Expenditures.cshtml) the value gets nulled, the pages does load again with the required route and all rest works, but the value for PersonnelNo is lost
is there a parameter that I should add to be passed again?
the complete version will have several "clickable" cells that will update data and "refresh" onto the same page, and I have some Partial views that I want to interchange based on the value of PersonnelNo, so I need to have this value persisting when the user clicks on "Save" but also when it clicks on the tags in the table
Adding small video showing issue:
https://imgur.com/mssr3y1
Add asp-route-personnelNo="#Model.PersonnelNo" into your <a> in Expenditures.cshtml too.
Result:

How to pass an array from one view and action to another in ASP.NET MVC?

What I want to achieve:
I want a feature that selects multiple records from the index page on my asp.net mvc website which are redirected to another webpage, where they can be bulk edited.
I am using checkboxes to do this, but I don't know how I can access the contents of this array from another action and view.
For now, this is the code that I have done:
The Index View:
#model IEnumerable<BulkDelete.Models.Employee>
#TempData["employeeIdsToDelete"];
<div style="font-family:Arial">
#using (Html.BeginForm("checking", "Home", FormMethod.Post))
{
<table class="table">
<thead>
<tr>
<td>Checkbox<br /></td>
<th>Name</th>
<th>Gender</th>
<th>Email</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td><input type="checkbox" name="employeeIdsToDelete" id="employeeIdsToDelete" value="#item.ID" /></td>
<td>#item.Name</td>
<td>#item.Gender</td>
<td>#item.Email</td>
</tr>
}
</tbody>
<br />
</table>
<input type="submit" value="Delete selected employees" />
}
</div>
The Controller Actions:
public class HomeController : Controller
{
SampleDBContext db = new SampleDBContext();
public ActionResult Index()
{
return View(db.Employees.ToList()) ;
}
[HttpPost]
public ActionResult UpdateMultipleRecords(IEnumerable<int> employeeIdsToUpdate)
{
return checking(employeeIdsToUpdate);
}
public ActionResult checking(IEnumerable<int>employeeIdsToUpdate)
{
foreach (var e in employeeIdsToUpdate)
{
Employee em = new Employee();
em = db.Employees.Find(e);
em.Name = ViewBag.Signature;
db.Entry(em).State = EntityState.Modified;
db.SaveChanges();
}
return View();
}
Instead of posting to index page. post the form in another page.
#using (Html.BeginForm("{actionNameOfAnotherPage}", "{anotherPageControllerName}", FormMethod.Post))
As you are posting an array of data, Use array indexing for form inputs.
#{ var index = 0;}
#foreach (var item in Model)
{
<tr>
<td><input type="checkbox" name="[#index]employeeIdsToDelete" class="mycheckbox" id="employeeIdsToDelete" value="#item.ID" /></td>
<td>#item.Name</td>
<td>#item.Gender</td>
<td>#item.Email</td>
</tr>
#{
index++;
}
}
You can also send them as an Ajax call, and in this scenerio you can also do this without a form.
<div style="font-family:Arial">
<table class="table">
<thead>
<tr>
<td>Checkbox<br /></td>
<th>Name</th>
<th>Gender</th>
<th>Email</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td><input type="checkbox" name="employeeIdsToDelete" class="mycheckbox" id="employeeIdsToDelete" value="#item.ID" /></td>
<td>#item.Name</td>
<td>#item.Gender</td>
<td>#item.Email</td>
</tr>
}
</tbody>
<br />
</table>
<input type="button" onClick="deleteEmployees()" value="Delete selected employees" />
</div>
and below your html page
<script>
function deleteEmployees() {
var checkboxes = document.getElementsByClassName("mycheckbox");
var checkedValues = [];
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
console.log(i);
checkedValues.push(checkboxes[i].value)
}
}
console.log(checkedValues);
$.ajax({
type: "POST",
url: '#Url.Action("YourAction", "YourController")',
data: { employeeIdsToUpdate: checkedValues },
success: function (res) {
if (res.ok) {
window.location = res.returnURL;
}
},
error: function (req, status, error) {
console.log(error);
}
});
}
</script>
and in your POST ACTION you can take these values
[POST]
public ActionResult UpdateEmployees(int[] employeeIdsToUpdate)
{
foreach (int e in employeeIdsToUpdate)
{
Employee em = new Employee();
em = db.Employees.Find(e);
em.Name = ViewBag.Signature;
db.Entry(em).State = EntityState.Modified;
db.SaveChanges();
}
return Json(new { ok = true, returnURL = Url.Action(nameof("YOUR VIEW IN HERE"))});
}
Here are some options for sending array from a View to some controller action:
Post the form to your controller action
Send ajax call to your controller action
MVC Hyperlink pointing to your controller action
Here are some options for sending array from a controller action to some other controller action:
Save data in TempData (its available across controllers)
Save data in browser memory (Temp Storage)
Save data in DB
Save data in Session object
Save data in Cache object
and Please dont forget to mark this as answer ... thanks

Multiple buttons on view - different methods

I have a view in my asp.net (Core) app, which displays a shopping cart. Typical shopping cart stuff - it lists the items in the cart. Each row has an editable field to allow changing the quantity, and an editable checkbox to allow the user to mark the row as "Delete".
I want two buttons at the bottom of the view, one for "Update Cart" (this will delete rows, and update quantities, etc). The other is for "Create Order", which will do the same as "Update Cart", but also create the order.
I have been able to create one button, and have it call the appropriate method in the controller, but I'm having trouble adding a second button, and have it call a different method.
At the moment, my view looks like this:
#model CustomerPortal.Models.StoreViewModels.CartViewModel
#{
ViewData["Title"] = "View";
}
<h2>Shopping Cart</h2>
<form asp-action="UpdateCart">
<table class="table">
<thead>
<tr>
<th>Item</th>
<th>Size</th>
<th>Colour</th>
<th>Quantity</th>
<th>Unit Price Inc. GST</th>
<th>Line Amount Inc. GST</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.CartLines.Count; i++)
{
<tr>
#Html.HiddenFor(modelItem => modelItem.CartLines[i].CartLine.ID)
<td>#Html.DisplayFor(modelItem => modelItem.CartLines[i].CartLine.Item.Description)</td>
<td>#Html.DisplayFor(modelItem => modelItem.CartLines[i].CartLine.ItemSize.Name)</td>
<td>#Html.DisplayFor(modelItem => modelItem.CartLines[i].CartLine.ItemColour.Name)</td>
<td>#Html.EditorFor(modelItem => modelItem.CartLines[i].CartLine.Quantity)</td>
<td>#Html.DisplayFor(modelItem => modelItem.CartLines[i].UnitPriceInecTax)</td>
<td>#Html.DisplayFor(modelItem => modelItem.CartLines[i].LineAmountIncTax)</td>
<td>#Html.EditorFor(modelItem => modelItem.CartLines[i].Delete)</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Update Cart" class="btn btn-default"/>
<input type="submit" value="Create Order" class="btn btn-default"/>
</div>
</form>
And the methods in the ShoppingCartController look like this:
[HttpPost, ActionName("UpdateCart")]
[Authorize]
public async Task<IActionResult> UpdateCart(CartViewModel cart)
{
// Need to do some stuff here
return RedirectToAction("Index");
}
[HttpPost, ActionName("CreateOrder")]
[Authorize]
public async Task<IActionResult> CreateOrder(CartViewModel cart)
{
// Need to do some stuff here
return RedirectToAction("Index");
}
I've tried using "#using(Html.BeginForm" without success. The current format (as above) is the only way that it "kind of" works (both buttons call one function).
So - how can I do this? How can I make both buttons call different methods? Or maybe call the same method with a different second parameter (indicating if I create order or not).
Thanks.
You can use html5 formaction attribute to submit to two different urls from the same form.
<form asp-action="UpdateCart">
<!-- your form elements -->
<div class="form-group">
<input type="submit" value="Update Cart" formaction="#Url.Action("UpdateCart")"/>
<input type="submit" value="Create Order" formaction="#Url.Action("CreateOrder")"/>
</div>
</form>
When you specify the formaction attribute on a submit button, it will overrides the parent form's action attribute value.
Another option is hijack the button click event in javascript and set the parent forms action attribute value to the one you want and trigger a submit event, as explained in this post

pass updated value to controller from view [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 6 years ago.
I am developing MVC 4 application in which I need to pass the updated value from the view to controller.
#foreach (var item in Model)
{
<tr>
<td>#item.ProductId</td>
<td>#item.Product.ProductName</td>
<td>#item.Product.UnitPrice</td>
<td>
#Html.TextBox("QuantityBox", item.Quantity)
</td>
</tr>
}
//Update Button
#using (Html.BeginForm("UpdateCart", "Cart", FormMethod.Post))
{
#Html.AntiForgeryToken()
<input type="submit" id="submit" name="Update" value="Update" />
}
The values could be entered different for different rows.
I need to pass these values from quantity textbox to controller.
Try using a for loop
#using (Html.BeginForm("UpdateCart", "Cart", FormMethod.Post))
{
#for(int idx = 0;idx < Model.Length;idx++)
{
<tr>
<td>#Model[idx].ProductId</td>
<td>#Model[idx].Product.ProductName</td>
<td>#Model[idx].Product.UnitPrice</td>
<td>
#Html.TextBoxFor(_ => Model[idx].Quantity)
</td>
</tr>
}
#Html.AntiForgeryToken()
<input type="submit" id="submit" name="Update" value="Update" />
}
When you post the above back to the controller, the MVC model binder will see the textboxes for Model[0].Quantity, Model[1].Quantity, etc and try to bind them to the incoming model. Using a foreach loop will result in none of that data being passed back to the controller.
I do not know what #model you are using on your view, but I'm assuming the MVC model binder will be able to deal with this.
Write like this..
#using (Html.BeginForm("UpdateCart", "Cart", FormMethod.Post))
{
#Html.AntiForgeryToken()
#foreach (var item in Model)
{
<tr>
<td>#item.ProductId</td>
<td>#item.Product.ProductName</td>
<td>#item.Product.UnitPrice</td>
<td>`enter code here`
#Html.TextBox("QuantityBox", item.Quantity)
</td>
</tr>
}
<input type="submit" id="submit" name="Update" value="Update" />
}
I would move the entire Foreach Codeblock
#foreach (var item in Model)
{
<tr>
<td>#item.ProductId</td>
<td>#item.Product.ProductName</td>
<td>#item.Product.UnitPrice</td>
<td>
#Html.TextBox("QuantityBox", item.Quantity)
</td>
</tr>
}
Into the Using statement : Otherwise your model back in the controller would not include the updated values that the user submmitted in the view.
In other words : the values that you want in your controller has to be in your Using statement where your submit button is located.
Your code should be like below
#using (Html.BeginForm("UpdateCart", "Cart", FormMethod.Post))
{
#for(i=0;i< Model.item.count ;i++)
{
<tr>
<td>#item.ProductId</td>
<td>#item.Product.ProductName</td>
<td>#item.Product.UnitPrice</td>
<td>
#Html.TextBoxFor(m=>m.item[i].Quantity)
</td>
</tr>
}
//Update Button
#Html.AntiForgeryToken()
<input type="submit" id="submit" name="Update" value="Update" />
}
Then you will get those values in controller through posting
Need to use form tag properly let me know if it works
If you want to know about url rewriting in MVC-4 please visit http://grandhah.blogspot.in/

Categories