To be more specific let me explain what did I encounter.
I was trying to submit a List of data from view to controller. I was able to submit some data successfully without any problem. But the problem arises when the data is more than around a list of 250 items and more than that. When I click a submit button it passes a NULL value when I debug it. There is no error with my code because I have submitted a list of 100 items to the controller without any problem. I guess there will be something that I have to specify so that It will also send a large number of lists.
Here I'm not using ajax or any javascript code to submit the form. I'm submitting it directly to the controller using post request.
I have posted some snippet of my code below to describe it more precisely.
View
<form method="post" action="SubmitList">
<div class="row">
<div class="col-md-12" style="padding-top:1%">
<input type="submit" value="PASS" class="btn btn-primary" style="float:right;" />
<div class="box-body">
<table id="#example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>No</th>
<th>Name</th>
</tr>
</thead>
<tbody>
#{
int i = 1;
}
#for (int j = 0; j < Model.Count(); j++)
{
<tr>
<td>#Html.Raw(i++)</td>
#Html.HiddenFor(item => item[j].Id, new { htmlAttributes = new { #class = "form-control" } })
<td>
#Html.DisplayFor(item => item[j].FullName)
</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
</div>
</div>
</div>
</form>
Controller
[AuthorizedAction]
[HttpPost]
public async Task<IActionResult> SubmitList(List<Student> students)
{
////
}
Can you tell me what's wrong with my code, please
Please try this in Startup#ConfigureServices
services.Configure<FormOptions>(options => options.ValueCountLimit = 1000); // you may want to adjust this limit
Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.formoptions
Related
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 created a view with two tabs.
One of the tabs has to be split in multiple pages.
Don't know how to do that with pagination.
Do i need a separate view for each tab or it can still be done in one view?
Can you please help me with an example for the same view?
<div class="w3-bar w3-dark-grey">
<button class="w3-bar-item w3-button tablink w3-red" onclick="openTab(event,'Statistics')">Statistics</button>
<button class="w3-bar-item w3-button tablink" onclick="openTab(event, 'History')">History</button>
</div>
<div id="History" class="w3-container w3-border city" style="display:none">
<h2>History</h2>
#helper ShowHistory(Site.classes.HistoryStruct[] aHistory)
{
<br>
<table class="table table-hover table-bordered">
<thead>
#if (aHistory == null)
{
<tr></tr>
}
else
{
<tr>
<th>Username</th>
<th>Info</th>
<th>Date</th>
</tr>
}
</thead>
<tbody id="tblStatistics">
#{string sClass = "class='divaproved'";
for (int i = 0; i < aHistory.Length; i++)
{
<tr #Html.Raw(sClass)>
<td>#aHistory[i].UserName</td>
<td>#aHistory[i].Info</td>
<td>#aHistory[i].Date</td>
</tr>
}
}
</tbody>
</table>
}
<div class="container">
#ShowHistory(Site.classes.UserAccessDB.aHistory)
</div>
</div>
If you just need to paginate your table you can try this js: DataTables
Give an ID to the table you need to paginate and add the following code at the end of your page
$(document).ready( function ()
{
$('#myTable').DataTable();
}
);
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 a Web App written in MVC5 which passes a List of an Object(which contains many items) to a Page and renders successfully. I have a button on the form that forces a Post back. When I click the button the model appears to re-initialise the List of Objects, rather than return what was is on the Page.
I have read various posts on SO that cover similar issues that have made many suggestion like ensuring every item in the Object is on the form (at least hidden). Tried many of the options, but so far haven't been successful in solving my issue.
I decided to go back to basics on it, and created a very simple View Model with a List. This again renders ok, but when returned it as System.Collections.Generic.List.
View Model
public class TestVm
{
public List<string> CustomerNames { get; set; }
}
Controller
public ActionResult Index()
{
TestVm testmodel = new TestVm();
testmodel.CustomerNames = new List<string>();
testmodel.CustomerNames.Add("HELP");
testmodel.CustomerNames.Add("Its");
testmodel.CustomerNames.Add("Not");
testmodel.CustomerNames.Add("Working");
return View(testmodel);
}
[HttpPost]
public ActionResult Index(TestVm model)
{
// DO SOME WORK HERE WITH RETURNED model
return View(model);
}
View
#model WebApplication1.Models.TestVm
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>View</title>
</head>
<body>
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
}
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
</body>
</html>
I thought creating a simple app like this would help me understand and solve my issue. But I can't work out why the model in the HttpPost contains "System.Collections.Generic.List", rather than the actual list of string that I would expect.
Initial Loadup
Page when loaded up the first time
After Refresh
Page after I clicked Refresh
You need to enclose every form control under Html.BeginForm brackets
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
}