I have a list In my view. For each row, I view button and I am passing Id value as hidden. But when I click any button it is passing wrong hidden value to the controller. Always it passes the first-row hidden value to the controller.
View:
#foreach (var list in Model)
{
<div>
<div > #( ((int)1) + #Model.IndexOf(list)).</div>
<div >#list.details</div>
<div class="col-md-2 row-index">
<button class="btn btn-link" type="submit" name="action:view" id="view">View</button>
<input type="hidden" name="viewId" id="viewId" value="list.WId" />
</div>
</div>
}
Controller:
[HttpPost]
[MultipleButton(Name = "action", Argument = "view")]
public ActionResult ViewDetail(string viewId)
{
return RedirectToAction("ViewDetails");
}
To get all values you need to change the input value type in your controller to array of strings.
I hope that this solution can help you
[HttpPost]
[MultipleButton(Name = "action", Argument = "view")]
public ActionResult ViewDetail(string[] viewId)
{
return RedirectToAction("ViewDetails");
}
if you want to get the exact value you need to duplicate the form within your foreach
in this case you should write somthing like this :
#foreach (var list in Model)
{
<div>
<div > #( ((int)1) + #Model.IndexOf(list)).</div>
<div >#list.details</div>
<div class="col-md-2 row-index">
<form ... > // complete your form attributes
<button class="btn btn-link" type="submit" name="action:view" id="view">View</button>
<input type="hidden" name="viewId" id="viewId" value="list.WId" />
</form>
</div>
</div>
}
Note : You should delete the global form
You should have one form for each row. then you submit that row.
Otherwise as you state it passes first value.
You are setting each value to the same element ID (which is invalid anyway) and name. When you submit your form (which would be more helpful to fully answer your question) it is finding the first element that matches that criteria and submitting it.
There are multiple ways to resolve this such as the already mentioned form per entry but the other preference would be to modify you button to a div and add a click handler to pass the specific value to a js function which would then submit to the controller. Its a preference choice regarding how tightly coupled you want your front end. But the main problem is your element naming convention.
Related
I feel this may be an easy fix but I cannot seem to get around it. I have an ASP.NET Core web application and I'm using an ajax form to submit data to my controller for processing. The problem I have is that my checkboxes always pass in as "false" even though they may be checked.
I searched for some answers but nothing I've tried has worked. Things like using the checked property, ensuring the name is correct.
Can anyone shed some light on why the values are ignored when the form is submitted to the controller and how I can fix it?
Form
<form asp-action="CreateCustomView" asp-controller="Data"
data-ajax="true"
data-ajax-method="POST">
<div class="row">
<div class="col">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="ColName" name="ColName" checked>
<label class="custom-control-label" for="ColName">Name</label>
</div>
</div>
<button type="reset">Reset</button>
<button type="submit">Save changes</button>
</form>
Model
namespace MyProject.Data
{
public class GridView : BaseEntity
{
public string Name { get; set; }
public bool ColName { get; set; }
}
}
DataController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyProject.Data;
using Microsoft.AspNetCore.Mvc;
namespace MyProject.UI.Controllers
{
public class DataController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateCustomView(GridView data)
{
//This method is incomplete, there is a break at the ActionResult to see what values are passed into `data`. In production, the values would be handled and the changes would be saved.
return View();
}
}
}
I have a breakpoint on the method CreateCustomView to see what values are passed, no matter what checkboxes I check, they are always being passed as false.
Can anyone help with this rather strange problem?
Update
As per the answer supplied by #Reyan-Chougle, if you're using CORE you need to supply the input as follows:
<input asp-for="#Model.ColType" type="checkbox" />
When the page is rendered, the control is automatically given a hidden field. Here is what the HTML of the above control renders as:
<input type="checkbox" class="custom-control-input" id="ColType" value="true" data-val="true" data-val-required="The ColType field is required." name="ColType">
<input type="hidden" value="false" id="ColType" name="ColType">
As you can see it creates a hidden checkbox with a value of false. This means that, as a boolean type it always has a value of true or false on submission.
As you are using ASP.NET Core, it is recommend to use the Tag Helpers:
<div class="form-group">
<label asp-for="#Model.ColName"></label>
<input asp-for="#Model.ColName" type="checkbox" />
</div>
If not using asp-for attribute , you can modify your codes to add a hidden field. It will be submitted regardless whether the checkbox is checked or not. If the checkbox is checked, the posted value will be true,false. The model binder will correctly extract true from the value. Otherwise it will be false :
<input type="checkbox" class="custom-control-input" data-val="true" id="ColName" name="ColName" value="true" checked>
<label class="custom-control-label" for="ColName">Name</label>
<input name="ColName" type="hidden" value="false">
I ran into a similar issue. There is a form we have that contains many fields that include textboxes, select (dropdown) menus, checkboxes and of course labels. All fields save to the database properly EXCEPT for the three checkbox options we have. I just figured out the fix after fooling with it off and on for weeks. I hope this helps someone:
This is what the code used to look like:
$(this).attr('data-val', 'true');
This is what fixed the problem:
$(this).val(true);
Here is the whole function that includes the fix:
$('form').on('change', ':checkbox', function () {
if (this.checked) {
$(this).val(true);
}
else {
$(this).val(false);
}
});
try to use
#Html.CheckBox("ColName", true)
I am using jquery repeater in my form to dynamically add only a part of input group to form. I did try but couldn't get inputs bind to model, I went blind.
Here my model.
public class SuggestionCreateEditViewModel
{
public Guid[] DetectionId { get; set; }
public SuggestionCreateEditRepeatedModel[] Repeated { get; set; }
}
public class SuggestionCreateEditRepeatedModel
{
public Guid To { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
}
Form, I removed a lot of parts of form for brevity
<div class="col-lg-9 col-md-9 col-sm-12">
<select asp-for="DetectionId" asp-items="ViewBag.AllDetections" class="m-bootstrap-select m_selectpicker toValidate"
multiple data-actions-box="true" data-width="100%"></select>
</div>
<div class="col-lg-9 col-md-9 col-sm-12 input-group date">
<input name = "Repeated.Deadline" type="text" readonly class="form-control toValidate dtDueDate" />
</div>
<div class="col-lg-12 col-md-12 col-sm-12">
<textarea name = "Repeated.Description" class="form-control toValidate txtSuggestion" type="text" >
</textarea>
</div>
after adding a new repeated section to form and before posting it to server, if I form.serailizeArray() it returns collection like as follows (what jquery form repeater dynamically shape names I believe)
{name: "DetectionId", value: "afca1b82-0455-432e-c780-08d6ac38b012"}
{name: "[0][Repeated.To][]", value: "b1176b82-1c25-4d13-9283-df2b16735266"}
{name: "[0][Repeated.Deadline]", value: "04/04/2019"}
{name: "[0][Repeated.Description]", value: "<p>test 1</p>"}
{name: "[1][Repeated.To]", value: "188806d8-202a-4787-98a6-8dc060624d93"}
{name: "[1][Repeated.Deadline]", value: "05/04/2019"}
{name: "[1][Repeated.Description]", value: "<p>test 2</p>"}
and my controller
[HttpPost]
public IActionResult CreateSuggestion(SuggestionCreateEditViewModel model, IFormFile[] documents)
{...
controller couldn't get Repeated binded, only DetectionId is binded. How should I shape my model to get the data?
Here is a working demo for with jquery.repeater.js, pay attention to this line <div data-repeater-list="Repeated"> which will format the field like name="Repeated[0][Description]"
#model TestCore.Models.SuggestionCreateEditViewModel
#{
ViewData["Title"] = "Contact";
}
<form class="repeater" asp-action="CreateSuggestion" method="post">
<!--
The value given to the data-repeater-list attribute will be used as the
base of rewritten name attributes. In this example, the first
data-repeater-item's name attribute would become group-a[0][text-input],
and the second data-repeater-item would become group-a[1][text-input]
-->
<div data-repeater-list="Repeated">
<div data-repeater-item>
<div class="col-lg-9 col-md-9 col-sm-12">
<select asp-for="DetectionId" asp-items="ViewBag.AllDetections" class="m-bootstrap-select m_selectpicker toValidate"
multiple data-actions-box="true" data-width="100%"></select>
</div>
<div class="col-lg-12 col-md-12 col-sm-12">
<textarea name="Description" class="form-control toValidate txtSuggestion" type="text">
</textarea>
</div>
</div>
</div>
<input data-repeater-create type="button" value="Add" />
<input type="submit" value="Submit"/>
</form>
#section Scripts{
<!-- Import repeater js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.repeater/1.2.1/jquery.repeater.js"></script>
<script>
$(document).ready(function () {
$('.repeater').repeater({
// (Optional)
// start with an empty list of repeaters. Set your first (and only)
// "data-repeater-item" with style="display:none;" and pass the
// following configuration flag
initEmpty: true,
// (Optional)
// "show" is called just after an item is added. The item is hidden
// at this point. If a show callback is not given the item will
// have $(this).show() called on it.
show: function () {
$(this).slideDown();
},
// (Optional)
// "hide" is called when a user clicks on a data-repeater-delete
// element. The item is still visible. "hide" is passed a function
// as its first argument which will properly remove the item.
// "hide" allows for a confirmation step, to send a delete request
// to the server, etc. If a hide callback is not given the item
// will be deleted.
hide: function (deleteElement) {
if (confirm('Are you sure you want to delete this element?')) {
$(this).slideUp(deleteElement);
}
},
// (Optional)
// Removes the delete button from the first list item,
// defaults to false.
isFirstItemUndeletable: true
})
});
</script>
}
By the looks of things, the controller cannot bind the repeater properties back to your view model because the naming of the posted content does not match the naming in your view model (as Topher mentioned).
The DetectionId is named correctly though because the name of the property matches and its not an array.
To resolve an array we need to make sure we include the property name in the form as well as an index so that mvc model binding knows where to bind the result to.
With that, can you try changing the format of the name to:
Repeated[0].To
That should match up with your controller and correctly bind.
For more info on binding, please see this.
In a Asp.net MVC view, i created a form, with a input field.
The user Sets a first name (or part of it), presses the submit button.
This is the form section:
<div>
<form action="SearchCustomer" methos="post">
Enter first name: <input id="Text1" name="txtFirstName" type="text" />
<br />
<input id="Submit1" type="submit" value="Search Customer" />
</form>
</div>
This is the SearchCustomer in the Controller, that gets the data from the form:
CustomerDal dal = new CustomerDal();
string searchValue = Request.Form["txtFirstName"].ToString();
List<Customer> customers = (from x in dal.Customers
where x.FirstName.Contains(searchValue)
select x).ToList<Customer>();
CustomerModelView customerModelView = new CustomerModelView();
customerModelView.Customers = customers;
return View("ShowSearch", customerModelView);
When i run the program, and enter a first name ("Jhon" for example), the code returns to SearchCustomer function, but Request.Form is empty.
Why?
Thanks.
Your method is spelled wrongly should not read methos but method like below:
<form action="SearchCustomer" method="post">
....
</form>
You need to modify your code:
you need to provide a action name here, which should be defined in your controller(SearchController) with the same name as 'ActionName' you will put in the below code.
if SearchController is your action name then provide the controller in which the action is available.
<div>
<form action="SearchCustomer/<ActionName>" method="post">
Enter first name: <input id="Text1" name="txtFirstName" type="text" />
<br />
<input id="Submit1" type="submit" value="Search Customer" />
</form>
</div>
With Html.BeginForm :
#using (Html.BeginForm("<ActionName>","<ControllerName>", FormMethod.Post))
{
Enter first name: <input id="Text1" name="txtFirstName" type="text" />
<br />
<input id="Submit1" type="submit" value="Search Customer" />
}
Set [HttpPost] on your controller.
[HttpPost]
public ActionResult SearchFunction(string txtFirstName)
{
CustomerDal dal = new CustomerDal();
string searchValue = txtFirstName;
List<Customer> customers = (from x in dal.Customers
where x.FirstName.Contains(searchValue)
select x).ToList<Customer>();
CustomerModelView customerModelView = new CustomerModelView();
customerModelView.Customers = customers;
return View("ShowSearch", customerModelView);
}
If you View is the same name as your ActionResult method, try this:
#using(Html.BeginForm())
{
... enter code
}
By default, it'll already be a POST method type and it'll be directed to the ActionResult. One thing to make sure of: You will need the [HttpPost] attribute on your ActionResult method so the form knows where to go:
[HttpPost]
public ActionResult SearchCustomer (FormCollection form)
{
// Pull from the form collection
string searchCriteria = Convert.ToString(form["txtFirstName"]);
// Or pull directly from the HttpRequest
string searchCriteria = Convert.ToString(Request["txtFirstName"]);
.. continue code
}
I hope this helps!
this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}
I have the following code, only the first form submits anything, the following submit null values, each model has data. If I change it to just one large form, everything submits. Why do the other individual forms post null values?
View
#model myModel[]
<ul>
#for (int i = 0; i < Model.Length; i++)
{
using (Html.BeginForm("controllerAction", "Controller", FormMethod.Post,
new { id="Form"+i }))
{
<li>
#Html.TextBoxFor(a => a[i].property1)
#Html.CheckBoxFor(a => a[i].property2)
#Html.HiddenFor(a => a[i].property3)
<input type="submit" />
</li>
}
}
</ul>
Controller
[HttpPost]
public ActionResult controllerAction(myModel[] models)
{
...do stuff...
}
The reason is that your creating form controls with indexers in your for loop, and your POST method parameter is myModel[] models.
By default, the DefaultModelBinder requires collection to be zero based and consecutive, so if you attempt to submit the second form, your posting back [1].property1: someValue etc. Because the indexer starts at 1, binding fails and the model is null.
You can solve this by adding a hidden input for an Index property used by the model binder to match up non consecutive indexers
<li>
#Html.TextBoxFor(a => a[i].property1)
#Html.CheckBoxFor(a => a[i].property2)
#Html.HiddenFor(a => a[i].property3)
<input type="hidden" name="Index" value="#i" /> // add this
<input type="submit" />
</li>