I have a table that has one empty column into which user can enter a comment:
Table
-----
TbMapId | UniqueAdp | Dealership | Line
--------------------------------------------------------------------
1 | [Insert comment here] | Derby | abc123
2 | [Insert comment here] | Keighley | cda345
3 | [Insert comment here] | Manchester | 876ghj
There is a lot of data to comment on, I can't expect a user to open an 'Edit' page and type in a comment one by one. Instead I need user to be able to input a bunch of comments (say 20 at a time against 20 rows) and save them all at one click of submit button.
If you want to jump straight to working solution go to EDIT #2 & look at Rudi's accepted answer
View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.TBMapBalancesList.Count; i++)
{
<tr>
<td>
@Html.DisplayFor(Model => Model.TBMapBalancesList[i].TbMapId)
@Html.HiddenFor(Model => Model.TBMapBalancesList[i].TbMapId)
</td>
<td>@Html.EditorFor(Model => Model.TBMapBalancesList[i].UniqueAdp, new { @class = "control-label_DI" })</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].AccountsCode)</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].Line)</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalancesList[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Model
I've learned today that I need to use List to be able to iterate through the lines in table by the use of @for
loop (as shown above). before I was trying to use IEnumerable. So I added a definition to the model for public List<TBMapBalances> TBMapBalancesList { get; set; }
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapBalances> TBMapBalances { get; set; }
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalancesList { get; set; }
[...]
}
Controller:
Now this is where I need the help with, my code doesn't throw any errors at all. When I press Submit nothing happens:
[Authorize]
[HttpPost]
public async Task<IActionResult> TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
}
return RedirectToAction("TbMapView");
}
EDIT #1
Changes to View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.TBMapBalances.Count; i++)
{
<tr>
<td>
@Html.DisplayFor(Model => Model.TBMapBalances[i].TbMapId)
@Html.HiddenFor(Model => Model.TBMapBalances[i].TbMapId)
</td>
<td>@Html.EditorFor(Model => Model.TBMapBalances[i].UniqueAdp, new { @class = "control-label_DI" })</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalances[i].AccountsCode)</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalances[i].Line)</td>
<td>@Html.DisplayFor(Model => Model.TBMapBalances[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Changes to model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNET_Core_1_0.Models.TbMapViewModels
{
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; }
[...]
}
}
Changes to Controller:
Now this is where I need the help with, my code doesn't throw any errors at all when at the current state - when I press Submit nothing happens and no data gets saved to the database.
however, when you uncomment line _context.Update(tbMapViewModel.TBMapBalances); I get an error that List is not part of any Model and is not found.
Also, below code is something I wrote trying to follow this SO post: update-multiple-records-at-once-in-asp-net-mvc - Initially I was trying to make it Async but I was getting even more errors and couldn't continue. I thought I am going to follow it as closely as possible in hope that it will get me a working starting point.
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
// _context.Update(tbMapViewModel.TBMapBalances);
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
EDIT #2 - A hero to the rescue - big thanks to @RudiVisser for help
First of all if any of you guys are - like me - stuck using .net core 1.0.0 make sure you upgrade to the latest version first (1.1.7 lts). Part of my grief was that I was an USER 1.0 and did not upgrade my installation as fixes and additions kept coming out. Don't be that guy, like I was...
All below is thanks to Rudi's help:
View
@using (Html.BeginForm("TbMapViewEdit", "TbMap"))
{
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
@Html.EditorFor(m => m.TBMapBalances);
</tbody>
</table>
</div>
</div>
</div>
}
Put your "Method", "Controller" in (Html.BeginForm("TbMapViewEdit", "TbMap"))
otherwise the form POST action will be set to the current location.
Model
Truncated for brevity. I have view model with List that I will be saving the data to and one other table just for displaying some info.
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();
[...]
}
Controller
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.UniqueAdp = TbListId.UniqueAdp;
}
}
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
Error that I was making here is that I was trying to replace the key with essentially the copy of itself (Find ID of 1 and set it to ID of 1) instead of picking up on the actual one field that I needed to edit which in my case was UniqueAdp.
Then came the new thing to me, which was Editor Template:
Editor Template
Create a folder called EditorTemplates
in 'Shared' Folder under your 'Views' folder with the exact name of the model that you intend to edit. In my case the model was called TBMapBalances
so I created a TBMapBalances.cshtml
file inside the newly created folder, then pasted this (this was originally in my main view file):
@model ASPNET_Core_1_0.Models.TBMapBalances
<tr>
<td>
@Html.DisplayFor(Model => Model.TbMapId)
@Html.HiddenFor(Model => Model.TbMapId)
</td>
<td>@Html.EditorFor(Model => Model.UniqueAdp, new { @class = "control-label_DI" })</td>
<td>@Html.DisplayFor(Model => Model.AccountsCode)</td>
<td>@Html.DisplayFor(Model => Model.Line)</td>
<td>@Html.DisplayFor(Model => Model.MapResult)</td>
</tr>
By the way the new { @class = "control-label_DI" }
is there to supposedly add class to each created input field. This doesn't seem to work in .net core and is left there just as a reminder to myself that I need to do this somehow.
Research:
Update multiple records at once in asp.net mvc
https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
How to bind an Array in MVC Core
https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/
ASP.NET Core 1.0 POST IEnumerable<T> to controller