Razor Page中实现表单数据添加至绑定DataTable的服务器端列表并同步更新表格的技术问询
Solution to Add Rows to Grid and Save to Database in Razor Pages
Let's break down the implementation step-by-step to achieve your desired functionality:
1. Client-Side Updates (cshtml)
First, we'll modify the front-end to handle dynamic row addition, auto-calculate totals, and ensure form data binds correctly to your server-side list.
Updated cshtml Code
@{ ViewData["Title"] = "Create"; Layout = "~/Pages/Shared/_Layout.cshtml"; } <h1>Create</h1> <h4>Trans_Detail</h4> <hr /> <div class="row"> <div class="col-md-4"> <form method="post"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label class="control-label">Vendor</label> <select asp-for="VendorGUIDBind" class ="form-control" asp-items="ViewBag.Vendor_GUID" > </select> </div> <div class="form-group"> <label asp-for="tbl_AC_Trans_Detail.Cement_GUID" class="control-label">Cement</label> <select asp-for="tbl_AC_Trans_Detail.Cement_GUID" class ="form-control" asp-items="ViewBag.Cement_GUID" id="ddlCement"> <option value="">Select Cement</option> </select> </div> <div class="form-group"> <label asp-for="tbl_AC_Trans_Detail.Cement_Type_GUID" class="control-label">Cement Type</label> <select asp-for="tbl_AC_Trans_Detail.Cement_Type_GUID" class ="form-control" asp-items="ViewBag.Cement_Type_GUID" id="ddlCementType"> <option value="">Select Cement Type</option> </select> </div> <div class="form-group"> <label asp-for="tbl_AC_Trans_Detail.Cement_Quantity" class="control-label">Cement Quantity</label> <input asp-for="tbl_AC_Trans_Detail.Cement_Quantity" class="form-control" id="txtCementQuantity"/> <span asp-validation-for="tbl_AC_Trans_Detail.Cement_Quantity" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="tbl_AC_Trans_Detail.Cement_Price" class="control-label">Cement Price</label> <input asp-for="tbl_AC_Trans_Detail.Cement_Price" class="form-control" id="txtCementPrice" /> <span asp-validation-for="tbl_AC_Trans_Detail.Cement_Price" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="tbl_AC_Trans_Detail.Total_Amount" class="control-label">Total Amount</label> <input asp-for="tbl_AC_Trans_Detail.Total_Amount" class="form-control" id="txtTotalAmount" readonly /> <span asp-validation-for="tbl_AC_Trans_Detail.Total_Amount" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> <input type="button" value="Add Row" class="btn btn-secondary" onclick="AddProduct()" /> </div> </form> </div> </div> <table id="example2" class="display" cellspacing="0" width="100%"> <thead> <tr> <th>Cement</th> <th>Cement Type</th> <th>Quantity</th> <th>Cement Price</th> <th>Total</th> </tr> </thead> <tbody> @for (int i = 0; i < Model.gridBind.Count; i++) { <tr> <td>@Html.DisplayFor(modelItem => Model.gridBind[i].Cement_GU.Cement_Name)</td> <td>@Html.DisplayFor(modelItem => Model.gridBind[i].Cement_Type_GU.Cement_Type_Name)</td> <td>@Html.DisplayFor(modelItem => Model.gridBind[i].Cement_Quantity)</td> <td>@Html.DisplayFor(modelItem => Model.gridBind[i].Cement_Price)</td> <td>@Html.DisplayFor(modelItem => Model.gridBind[i].Total_Amount)</td> <!-- Hidden inputs for model binding --> <td style="display:none;"> @Html.HiddenFor(modelItem => Model.gridBind[i].AC_Trans_Detail_GUID) @Html.HiddenFor(modelItem => Model.gridBind[i].Cement_GUID) @Html.HiddenFor(modelItem => Model.gridBind[i].Cement_Type_GUID) @Html.HiddenFor(modelItem => Model.gridBind[i].Cement_Quantity) @Html.HiddenFor(modelItem => Model.gridBind[i].Cement_Price) @Html.HiddenFor(modelItem => Model.gridBind[i].Total_Amount) </td> </tr> } </tbody> </table> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script> <link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet" /> <script> $(document).ready(function() { // Initialize DataTable $('#example2').DataTable(); // Auto-calculate total amount when quantity/price changes $('#txtCementQuantity, #txtCementPrice').on('input', function() { var quantity = parseFloat($('#txtCementQuantity').val()) || 0; var price = parseFloat($('#txtCementPrice').val()) || 0; $('#txtTotalAmount').val((quantity * price).toFixed(2)); }); // Populate Cement Type dropdown when Cement is selected $('#ddlCement').change(function() { var cementGUID = $(this).val(); if (cementGUID) { $.get(`?handler=CementTypes&id=${cementGUID}`, function(data) { $('#ddlCementType').empty(); $('#ddlCementType').append('<option value="">Select Cement Type</option>'); $.each(data, function(index, item) { $('#ddlCementType').append(`<option value="${item.Cement_Type_GUID}">${item.Cement_Type_Name}</option>`); }); }); } else { $('#ddlCementType').empty(); $('#ddlCementType').append('<option value="">Select Cement Type</option>'); } }); }); function AddProduct() { // Get form values var cementGUID = $('#ddlCement').val(); var cementTypeGUID = $('#ddlCementType').val(); var cementQuantity = $('#txtCementQuantity').val(); var cementPrice = $('#txtCementPrice').val(); var totalAmount = $('#txtTotalAmount').val(); // Get display names for the table var cementName = $('#ddlCement option:selected').text(); var cementTypeName = $('#ddlCementType option:selected').text(); // Validate required fields if (!cementGUID || !cementTypeGUID || !cementQuantity || !cementPrice) { alert('Please fill all required fields (Cement, Cement Type, Quantity, Price)!'); return; } // Ensure numeric values are valid if (isNaN(parseFloat(cementQuantity)) || isNaN(parseFloat(cementPrice))) { alert('Quantity and Price must be valid numbers!'); return; } // Add row to DataTable var table = $('#example2').DataTable(); var nextIndex = table.rows().count(); table.row.add([ cementName, cementTypeName, cementQuantity, cementPrice, totalAmount ]).draw(); // Append hidden inputs to form for model binding var form = $('form'); var newGuid = generateGUID(); form.append(`<input type="hidden" name="gridBind[${nextIndex}].AC_Trans_Detail_GUID" value="${newGuid}" />`); form.append(`<input type="hidden" name="gridBind[${nextIndex}].Cement_GUID" value="${cementGUID}" />`); form.append(`<input type="hidden" name="gridBind[${nextIndex}].Cement_Type_GUID" value="${cementTypeGUID}" />`); form.append(`<input type="hidden" name="gridBind[${nextIndex}].Cement_Quantity" value="${cementQuantity}" />`); form.append(`<input type="hidden" name="gridBind[${nextIndex}].Cement_Price" value="${cementPrice}" />`); form.append(`<input type="hidden" name="gridBind[${nextIndex}].Total_Amount" value="${totalAmount}" />`); // Clear form fields $('#ddlCement').val('').trigger('change'); $('#txtCementQuantity').val(''); $('#txtCementPrice').val(''); $('#txtTotalAmount').val(''); } // Generate a valid GUID for new detail rows function generateGUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } </script> }
2. Server-Side Updates (cshtml.cs)
Next, we'll modify the backend to handle form submission, save the main transaction, and all associated details.
Updated cshtml.cs Code
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ArabianCement.Pages { public class CreateModel : PageModel { private readonly ArabianCement.Models.ArabianCementContext _context; private readonly IConfiguration _configuration; public CreateModel(ArabianCement.Models.ArabianCementContext context, IConfiguration configuration) { _context = context; _configuration = configuration; } [BindProperty] public Guid VendorGUIDBind { get; set; } [BindProperty] public List<tbl_AC_Trans_Detail> gridBind { get; set; } = new List<tbl_AC_Trans_Detail>(); [BindProperty] public tbl_AC_Trans_Detail tbl_AC_Trans_Detail { get; set; } public IActionResult OnGet() { ViewData["Cement_GUID"] = new SelectList(_context.LK_Cements, "Cement_GUID", "Cement_Name"); ViewData["Vendor_GUID"] = new SelectList(_context.LK_Sources, "Source_GUID", "Source_Name"); return Page(); } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { // Repopulate dropdowns if validation fails ViewData["Cement_GUID"] = new SelectList(_context.LK_Cements, "Cement_GUID", "Cement_Name", tbl_AC_Trans_Detail.Cement_GUID); ViewData["Vendor_GUID"] = new SelectList(_context.LK_Sources, "Source_GUID", "Source_Name", VendorGUIDBind); return Page(); } // Create main transaction record var mainTransaction = new tbl_AC_Tran { AC_Trans_GUID = Guid.NewGuid(), Creation_Date = DateTime.Now, Vendor_GUID = VendorGUIDBind, Trans_Type = Guid.Parse(_configuration["AppKeys:SellGUID"]) }; _context.tbl_AC_Trans.Add(mainTransaction); await _context.SaveChangesAsync(); // Add all detail records linked to the main transaction foreach (var detail in gridBind) { detail.AC_Trans_GUID = mainTransaction.AC_Trans_GUID; _context.tbl_AC_Trans_Details.Add(detail); } await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } public JsonResult OnGetCementTypes(string id) { if (!Guid.TryParse(id, out Guid cementGUID)) { return new JsonResult(new List<LK_Cement_Type>()); } var cementTypes = _context.LK_Cement_Types .Where(x => x.Cement_GUID == cementGUID) .Select(x => new { x.Cement_Type_GUID, x.Cement_Type_Name }) .ToList(); return new JsonResult(cementTypes); } } }
Key Features Explained
Dynamic Row Addition:
- The
AddProduct()function captures form values, validates them, adds a row to the DataTable, and appends hidden inputs to ensure the new row is bound to thegridBindlist on submission.
- The
Auto-Calculated Totals:
- JavaScript automatically computes the total amount when quantity or price is changed, and the total input is set to readonly to prevent manual edits.
Model Binding:
- We use a
forloop instead offoreachto render table rows, which generates correctly indexed hidden inputs for model binding to thegridBindlist.
- We use a
Database Saving:
- The
OnPostAsyncmethod first saves the main transaction, then links all details fromgridBindto the main transaction and saves them to the database.
- The
Dropdown Dependencies:
- The Cement Type dropdown is populated dynamically when a Cement is selected using AJAX to call the
OnGetCementTypeshandler.
- The Cement Type dropdown is populated dynamically when a Cement is selected using AJAX to call the
内容的提问来源于stack exchange,提问作者Allam
相关产品推荐
相关产品推荐

