C# MVC ASP.NET: Write Multiple Lines from Form to DB Table
Tags: csharp, mvc
Published: 2023-02-16 15:30:21

Greetings all,

Brief Description:
I am working on an Inventory Management system, which focuses on tracking items by S/N, shipments In/Out, & service history. I am building this via Visual Studio in C# MVC ASP.NET.

I currently have a working system, with the basic CRUD as well as some more advanced Logic CRUD. The part I am bogged down on now is the process of taking multiple lines of user entered data from a View/Form and then writing that to a table.

I have done extensive searches on G00gle, SO, CodeProject, MSDN, etc. All have viable examples, but none that quite fit my scenario. Everything seems to be related to pulling data already in a table to write it to a view, whereas I want to do just the opposite.

Here is the Scenario:
One of my Views is a page that allows a user to create a new Transfer. This is an Inbound/Outbound shipment, which will list the units/items that are Inbound/Outbound. The user will enter certain specific information as well as create a listing of all the items currently associated with that shipment number.
On this form, I have built a dynamic feature that will allow a user to add lines to the form on the fly.
Once the user has entered all the items, I want to gather all of the item information and then write it to a table in the DB.
To my understanding, this needs to be done with a List<xxx>, but this is where I am stuck.

The Code Blocks:
**-- RXDoc Data Table**

```
public class RXDoc
    {
        public int Id { get; set; }

        [ForeignKey("TransferId")]
        [Display(Name = "TRK #")]
        public ItemTransfer? ItemTransfer { get; set; }
        public int TransferId { get; set; }
        //public string TransferNumber { get; set; }      // This STRING to be set as the MT# in the EXCEL SHEET cellR1->U2

        //[Required]
        [ForeignKey("ClientId")]
        [Display(Name = "Client")]
        public Client? Client { get; set; }
        public int ClientId { get; set; }
        //public string ClientName { get; set; }          // This STRING to be set as the CLIENT NAME (CONSIGNEE/RECIPIENT) in EXCEL SHEET cellN3->V3

        //// TO address of RECIPIENT/CONSIGNEE
        public string? AddressLine1 { get; set; }
        public string? AddressLine2 { get; set; }
        public string? AddressLine3 { get; set; }
        public string? AddressLine4 { get; set; }
        public string? AddressLine5 { get; set; }

        [Display(Name = "Ship Date")]
        [DataType(DataType.Date)]
        public DateTime? ShipDate { get; set; }     // date of creation

        [Display(Name = "Our Ref#")]
        public string? OurRefNum { get; set; }

        [Display(Name = "Ship Via")]
        public string? CourierName { get; set; }

        [Display(Name = "TRK #")]
        public string? CourierTrkNum { get; set; }

        [Display(Name = "PO #")]
        public string? PONumber { get; set; }

        [Display(Name = "Ref #")]
        public string? ClientRefNum { get; set; }

        [Display(Name = "# Items Shipped")]
        public int? TotalItems { get; set; }

        [Display(Name = "# Units Shipped")]
        public int? TotalUnits { get; set; }         // The total units released i.e. # of pallets in shipment

        [Display(Name = "Unit Type")]
        public string? UnitType { get; set; }       // PAL, CRT, BOX, CON, SKD, UNIT, etc.

        [Display(Name = "Shipped by")]
        public string? ShipperName { get; set; }

        [Display(Name = "Received by")]
        public string? DriverName { get; set; }

        [Display(Name = "Release Date")]
        public string? ReleaseDate { get; set; }    // date of shipment leaving facility


        public int? BoxNumber { get; set; }
        public int? Qty { get; set; }
        public string? PartNumber { get; set; }
        public string? Description { get; set; }
        public string? SerialNumber { get; set; }
        public string? Notes { get; set; }
    }
```

**-- RXCreateVM Model**

```
public class RXCreateVM
    {
        public int Id { get; set; }

        [ForeignKey("TransferId")]
        [Display(Name = "TRK #")]
        public ItemTransfer? ItemTransfer { get; set; }
        public int TransferId { get; set; }
        public string TransferNumber { get; set; }      // This STRING to be set as the MT# in the EXCEL SHEET cellR1->U2

        //[Required]
        [ForeignKey("ClientId")]
        [Display(Name = "Client")]
        public Client? Client { get; set; }
        public int ClientId { get; set; }
        public string ClientName { get; set; }          // This STRING to be set as the CLIENT NAME (CONSIGNEE/RECIPIENT) in EXCEL SHEET cellN3->V3

        //// TO address of RECIPIENT/CONSIGNEE
        public string? AddressLine1 { get; set; }
        public string? AddressLine2 { get; set; }
        public string? AddressLine3 { get; set; }
        public string? AddressLine4 { get; set; }
        public string? AddressLine5 { get; set; }

        [Display(Name = "Ship Date")]
        [DataType(DataType.Date)]
        public DateTime? ShipDate { get; set; }     // date of creation

        [Display(Name = "Our Ref#")]
        public string? OurRefNum { get; set; }

        [Display(Name = "Ship Via")]
        public string? CourierName { get; set; }

        [Display(Name = "TRK #")]
        public string? CourierTrkNum { get; set; }

        [Display(Name = "PO #")]
        public string? PONumber { get; set; }

        [Display(Name = "Ref #")]
        public string? ClientRefNum { get; set; }

        [Display(Name = "# Items Shipped")]
        public int? TotalItems { get; set; }

        [Display(Name = "# Units Shipped")]
        public int? TotalUnits { get; set; }         // The total units released i.e. # of pallets in shipment

        [Display(Name = "Unit Type")]
        public string? UnitType { get; set; }       // PAL, CRT, BOX, CON, SKD, UNIT, etc.

        [Display(Name = "Shipped by")]
        public string? ShipperName { get; set; }

        [Display(Name = "Received by")]
        public string? DriverName { get; set; }

        [Display(Name = "Release Date")]
        public string? ReleaseDate { get; set; }    // date of shipment leaving facility

        public int? Qty { get; set; }
        public int? BoxNumber { get; set; }
        public string? PartNumber { get; set; }
        public string? Description { get; set; }
        public string? SerialNumber { get; set; }
        public string? Notes { get; set; }

        public List<RXCreateVM> RXList { get; set; }
        public Client Clients { get; set; }
    }
```

**-- RXCreate View**
The javascript codeblock, at the bottom, handles the dynamic add/remove of the rows in the View
```
@model RXCreateVM

@{
    ViewData["Title"] = "Create";
}

<h1>New INCOMING Shipment</h1>
<hr />
<div class="container">
    <form asp-action="RXCreate">
        <div class="container">
            <div class="row justify-content-center" style="text-align:center;">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-2 form-group">
                    <label>Ship Date</label>
                    <input style="font-size:13px;" asp-for="ShipDate" class="form-control" />
                    <span asp-validation-for="ShipDate" class="text-danger"></span>
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-4 form-group">
                    <label asp-for="Client" class="control-label"></label>
                    <select style="font-size:13px;" asp-for="ClientId" class="form-select" asp-items="ViewBag?.ClientId">
                        <option>--Select--</option>
                    </select>
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                    <div class="col-sm-3 form-group">
                    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                    <label>Transfer #</label>
                    <select style="font-size:13px;" asp-for="TransferId" class="form-select" asp-items="ViewBag?.TransferId">
                        <option>--Select--</option>
                    </select>
                    </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-2 form-group">
                    <label>Our REF #</label>
                    <input style="font-size:13px;" asp-for="OurRefNum" class="form-control" />
                    <span asp-validation-for="OurRefNum" class="text-danger"></span>
                </div>
            </div>
        </div>
        <br />
        <div class="container">
            <div class="row justify-content-center" style="text-align:center;">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-2 form-group">
                    <label>Ship VIA</label>
                    <input style="font-size:13px;" asp-for="CourierName" class="form-control" />
                    <span asp-validation-for="CourierName" class="text-danger"></span>
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-3 form-group">
                    <label>TRK #</label>
                    <input style="font-size:13px;" asp-for="CourierTrkNum" class="form-control" />
                    <span asp-validation-for="CourierName" class="text-danger"></span>
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-3 form-group">
                    <label>Client PO #</label>
                    <input style="font-size:13px;" asp-for="PONumber" class="form-control" />
                    <span asp-validation-for="PONumber" class="text-danger"></span>
                </div>
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="col-sm-3 form-group">
                    <label>Client REF #</label>
                    <input style="font-size:13px;" asp-for="ClientRefNum" class="form-control" />
                    <span asp-validation-for="ClientRefNum" class="text-danger"></span>
                </div>
            </div>
        </div>
        <hr />
        <table id="submissionTable" class="tableRXCreate table-condensed center">
            <thead>
                <tr>
                    <th>BOX</th>
                    <th>QTY</th>
                    <th>P/N</th>
                    <th>DESCRIPTION</th>
                    <th>S/N</th>
                    <th>NOTES</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr id="tablerow0">
                    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                        <td class="form-group">
                        <input asp-for="BoxNumber" type="number" class="form-control tblInput CW60" />
                        <span asp-validation-for="BoxNumber" class="text-danger"></span>
                    </td>
                        <td class="form-group">
                        <input asp-for="Qty" type="number" class="tblInput CW60" />
                        <span asp-validation-for="Qty" class="text-danger"></span>
                    </td>
                        <td class="form-group">
                        <input asp-for="PartNumber" type="text" class="tblInput CW120" />
                        <span asp-validation-for="PartNumber" class="text-danger"></span>
                    </td>
                        <td class="form-group">
                        <input asp-for="Description" type="text" class="tblInput CW300" />
                        <span asp-validation-for="Description" class="text-danger"></span>
                    </td>
                        <td class="form-group">
                        <input asp-for="SerialNumber" type="text" class="tblInput CW120" />
                        <span asp-validation-for="SerialNumber" class="text-danger"></span>
                    </td>
                        <td class="form-group">
                        <input asp-for="Notes" type="text" class="tblInput CW200" />
                        <span asp-validation-for="Notes" class="text-danger"></span>
                    </td>
                    <td>&nbsp;<button type="button" class="btn btn-sm btn-outline-danger BR6" onclick="removeTr(0);"><i class="fa-solid fa-trash-can"></i></button></td>
                </tr>
            </tbody>
        </table>
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="TotalItems" class="control-label"></label>
            <input asp-for="TotalItems" class="form-control CW80" />
            <span asp-validation-for="TotalItems" class="text-danger"></span>
        </div>
        <hr />
        <button id="add" type="submit" class="btn btn-sm btn-outline-success BR6">Add Row</button>
        <hr />
        <div class="form-group">
            <div class="center">
                <input type="submit" value="CREATE Document" class="btn btn-sm btn-outline-warning BR6" />
            </div>
        </div>
    </form>
</div>

<!-- ================== BEGIN core-js ================== -->
@section Scripts {
    <script type="text/javascript">
        var counter = 1;
            $(function () {
                $('#add').click(function () {
                    $('<tr id="tablerow' + counter + '"><td>' +
                    '<input type="number" class="text-box single-line tblInput CW60" name="Box[' + counter + ']" value="" required="required" />' + '</td>' +
                    '<td>' + '<input type="number" class="text-box single-line tblInput CW60" name="Qty[' + counter + ']" value="" />' + '</td>' +
                    '<td>' + '<input type="text" class="text-box single-line tblInput CW120" name="PN[' + counter + ']" value="" />' + '</td>' +
                        '<td>' + '<input type="text" class="text-box single-line tblInput CW300" name="Description[' + counter + ']" value="" required="required" />' + '</td>' +
                        '<td>' + '<input type="text" class="text-box single-line tblInput CW120" name="SN[' + counter + ']" value="" />' + '</td>' +
                        '<td>' + '<input type="text" class="text-box single-line tblInput CW200" name="Notes[' + counter + ']" value="" />' + '</td>' +
                        '<td>' + '&nbsp;' + '<button type="button" class="btn btn-sm btn-outline-danger BR6" onclick="removeTr(' + counter + ');"><i class="fa-solid fa-trash-can"></i></button>' + '</td>' +
                        '</tr>').appendTo('#submissionTable');
                        counter++;
                        return false;
                    });
                });
                function removeTr(index) {
                    if (counter > 1) {
                        $('#tablerow' + index).remove();
                    counter--;
                }
            return false;
        }
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
}
<!-- ================== END core-js ================== -->
```

**-- Controller**

```
public async Task<IActionResult> RXDOC()
        {
            var prefixR = "R";
            var transfersList = await _context.ItemTransfers.Include(q => q.Client).Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(x => x.TrackingNumber).ToListAsync();

            var model = new RXTransferVM
            {
                TotalShipments = transfersList.Count,
                RXTransferList = mapper.Map<List<RXTransferVM>>(transfersList)
            };

            ViewData["ClientId"] = new SelectList(_context.Clients.OrderBy(c => c.Name), "Id", "Name");
            ViewData["TransferId"] = new SelectList(_context.ItemTransfers.Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(t => t.TrackingNumber), "Id", "TrackingNumber");
            return View(model);
        }

        public async Task<IActionResult> RXCreate()
        {
            var prefixR = "R";
            var transfersList = await _context.ItemTransfers.Include(q => q.Client).Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(x => x.TrackingNumber).ToListAsync();
            
            ViewData["ClientId"] = new SelectList(_context.Clients.OrderBy(c => c.Name), "Id", "Name");
            ViewData["TransferId"] = new SelectList(_context.ItemTransfers.Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(t => t.TrackingNumber), "Id", "TrackingNumber");
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> RXCreate(List<RXCreateVM> rxdocVM)
        {
            var prefixR = "R";
            var transfersList = await _context.ItemTransfers.Include(q => q.Client).Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(x => x.TrackingNumber).ToListAsync();
            //var clientName = await _context.Clients.FindAsync(rxdocVM.ClientId);

            var itemsList = new List<RXCreateVM>();
                        
            
            var newTransfer = mapper.Map<RXDoc>(rxdocVM);
            _context.RXDocs.Add(newTransfer);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
            
            //ViewData["ClientId"] = new SelectList(_context.Clients.OrderBy(c => c.Name), "Id", "Name");
            //ViewData["TransferId"] = new SelectList(_context.ItemTransfers.Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(t => t.TrackingNumber), "Id", "TrackingNumber");
            //return View();
        }
```
At this point, the code works, but will only add the first row to the DB table, even if other rows are filled in.

I have attempted different variations of examples I have found on SO, MSDN, CodeProject, and other sites... all of which illustrate using a List<>.
This is the point where I am getting stuck. The last attempt I made at using the List resulted in various 'not working' scenarios.
Here is a sample block that I tried:

```
[HttpPost]
        public async Task<IActionResult> RXCreate(RXDoc rxdocVM)
        {
            var prefixR = "R";
            var transfersList = await _context.ItemTransfers.Include(q => q.Client).Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(x => x.TrackingNumber).ToListAsync();
            var clientName = await _context.Clients.FindAsync(rxdocVM.ClientId);


            if (clientName == null)
            {
                return View();
            }
            if (ModelState.IsValid)
            {
                Console.WriteLine("State: VALID!");
                var model = new RXCreateVM
                {
                    ClientName = clientName.Name,
                    AddressLine1 = rxdocVM.AddressLine1,
                    AddressLine2 = rxdocVM.AddressLine2,
                    AddressLine3 = rxdocVM.AddressLine3,
                    AddressLine4 = rxdocVM.AddressLine4,
                    AddressLine5 = rxdocVM.AddressLine5,
                    ShipDate = rxdocVM.ShipDate,
                    OurRefNum = rxdocVM.OurRefNum,
                    CourierName = rxdocVM.CourierName,
                    CourierTrkNum = rxdocVM.CourierTrkNum,
                    PONumber = rxdocVM.PONumber,
                    ClientRefNum = rxdocVM.ClientRefNum,
                    TotalItems = rxdocVM.TotalItems,
                    TotalUnits = rxdocVM.TotalUnits,
                    UnitType = rxdocVM.UnitType,
                    ShipperName = rxdocVM.ShipperName,
                    DriverName = rxdocVM.DriverName,
                    ReleaseDate = rxdocVM.ReleaseDate,
                    Qty = rxdocVM.Qty,
                    BoxNumber = rxdocVM.BoxNumber,
                    PartNumber = rxdocVM.PartNumber,
                    Description = rxdocVM.Description,
                    SerialNumber = rxdocVM.SerialNumber,
                    Notes = rxdocVM.Notes,
                    RXList = mapper.Map<List<RXCreateVM>>(rxdocVM)
                };
                var newTransfer = mapper.Map<RXDoc>(rxdocVM);
                _context.RXDocs.Add(newTransfer);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            ViewData["ClientId"] = new SelectList(_context.Clients.OrderBy(c => c.Name), "Id", "Name");
            ViewData["TransferId"] = new SelectList(_context.ItemTransfers.Where(x => x.TrackingNumber.StartsWith(prefixR)).OrderByDescending(t => t.TrackingNumber), "Id", "TrackingNumber");
            return View();
        }
```
With the above attempt, I get mapping errors, which I assume is because I am calling the method with the Table reference but using  the VM as the model.

Do I need to restructure the Post Method by declaring it with List of the Table RXDoc or List of the Model RXCreateVM?
Does this also mean that I need to change the View's @model statement to use a List of RXCreateVM or List of RXDoc table?
This is where I am stuck; trying to figure out how to get the dynamically added rows to be scooped up by the controller once the Save button is clicked.

_NOTE_: I know that some of the data fields are not shown in the View, because I have made them Nullable for testing purposes so I don't have to type in all that info each time I test/debug, which is often. (i.e. _The address lines, etc. All other pertinent info is Required and, I think, accounted for_)

Any help/insight would be greatly appreciated.

Regards,
FriQ