如何在ASP.NET Core Razor Page中创建支持多详情记录的主详情页?
实现主详情结构的Razor页面方案
以下是创建包含主表展示、明细录入与保存功能的Razor页面的具体步骤:
1. 定义实体与数据库上下文
首先创建主表和明细表的实体类,配置好主外键关联:
// 主表实体:员工主信息 public class EmployeeHeader { [Required] public int EmpId { get; set; } [Required] [StringLength(50)] public string EmployeeType { get; set; } // 导航属性:关联当前主记录的所有明细 public List<EmployeeDetail> Details { get; set; } = new List<EmployeeDetail>(); } // 明细表实体:员工明细信息 public class EmployeeDetail { public int Id { get; set; } // 明细主键 public int EmpId { get; set; } // 外键,关联主表EmpId [Required] [StringLength(50)] public string FName { get; set; } [Required] [StringLength(50)] public string LName { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } // 导航属性:关联所属的主记录 public EmployeeHeader Header { get; set; } }
接着配置数据库上下文,确保主外键关系生效:
public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<EmployeeHeader> EmployeeHeaders { get; set; } public DbSet<EmployeeDetail> EmployeeDetails { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置主表与明细表的一对多关联 modelBuilder.Entity<EmployeeDetail>() .HasOne(d => d.Header) .WithMany(h => h.Details) .HasForeignKey(d => d.EmpId); } }
2. 创建主详情Razor页面
创建名为EmployeeForm.cshtml的页面,分为主信息区域和明细录入区域,支持动态添加/删除明细行:
@page @model EmployeeFormModel <h1>员工信息录入</h1> <!-- 主信息区域 --> <div class="card mb-4"> <div class="card-header">主信息</div> <div class="card-body"> <div class="row g-3"> <div class="col-md-6"> <label asp-for="Header.EmpId" class="form-label">员工ID</label> <input asp-for="Header.EmpId" class="form-control" /> <span asp-validation-for="Header.EmpId" class="text-danger"></span> </div> <div class="col-md-6"> <label asp-for="Header.EmployeeType" class="form-label">员工类型</label> <input asp-for="Header.EmployeeType" class="form-control" /> <span asp-validation-for="Header.EmployeeType" class="text-danger"></span> </div> </div> </div> </div> <!-- 明细信息区域 --> <div class="card"> <div class="card-header d-flex justify-content-between align-items-center"> <span>员工明细</span> <button type="button" class="btn btn-sm btn-primary" onclick="addDetailRow()">添加新明细</button> </div> <div class="card-body"> <form method="post"> <table class="table table-bordered" id="detailsTable"> <thead> <tr> <th>名</th> <th>姓</th> <th>地址</th> <th>城市</th> <th>州</th> <th>邮编</th> <th>操作</th> </tr> </thead> <tbody> <!-- 渲染已有的明细行(初始会有一条空行) --> @for (int i = 0; i < Model.Header.Details.Count; i++) { <tr> <td> <input asp-for="Header.Details[i].FName" class="form-control" /> <span asp-validation-for="Header.Details[i].FName" class="text-danger"></span> </td> <td> <input asp-for="Header.Details[i].LName" class="form-control" /> <span asp-validation-for="Header.Details[i].LName" class="text-danger"></span> </td> <td> <input asp-for="Header.Details[i].Address" class="form-control" /> </td> <td> <input asp-for="Header.Details[i].City" class="form-control" /> </td> <td> <input asp-for="Header.Details[i].State" class="form-control" /> </td> <td> <input asp-for="Header.Details[i].Zip" class="form-control" /> </td> <td> <button type="button" class="btn btn-sm btn-danger" onclick="removeRow(this)">删除</button> </td> </tr> } </tbody> </table> <button type="submit" class="btn btn-primary mt-3">保存所有信息</button> </form> </div> </div> <script> // 添加新的明细行 function addDetailRow() { const tableBody = document.getElementById('detailsTable').querySelector('tbody'); const rowCount = tableBody.children.length; // 动态生成符合模型绑定格式的输入框 const newRow = ` <tr> <td> <input name="Header.Details[${rowCount}].FName" class="form-control" required /> </td> <td> <input name="Header.Details[${rowCount}].LName" class="form-control" required /> </td> <td> <input name="Header.Details[${rowCount}].Address" class="form-control" /> </td> <td> <input name="Header.Details[${rowCount}].City" class="form-control" /> </td> <td> <input name="Header.Details[${rowCount}].State" class="form-control" /> </td> <td> <input name="Header.Details[${rowCount}].Zip" class="form-control" /> </td> <td> <button type="button" class="btn btn-sm btn-danger" onclick="removeRow(this)">删除</button> </td> </tr> `; tableBody.insertAdjacentHTML('beforeend', newRow); } // 删除明细行并重新索引,确保模型绑定正常 function removeRow(btn) { btn.closest('tr').remove(); const rows = document.getElementById('detailsTable').querySelector('tbody').children; for (let i = 0; i < rows.length; i++) { const inputs = rows[i].querySelectorAll('input'); inputs.forEach(input => { input.name = input.name.replace(/\[(\d+)\]/, `[${i}]`); }); } } </script>
3. 编写页面模型逻辑
创建EmployeeForm.cshtml.cs文件,处理页面初始化、表单提交与数据保存:
public class EmployeeFormModel : PageModel { private readonly AppDbContext _dbContext; public EmployeeFormModel(AppDbContext dbContext) { _dbContext = dbContext; } // 绑定主记录及其明细,自动处理模型绑定 [BindProperty] public EmployeeHeader Header { get; set; } = new EmployeeHeader(); public void OnGet() { // 初始化一条空明细,方便用户直接录入 Header.Details.Add(new EmployeeDetail()); } public async Task<IActionResult> OnPostAsync() { // 验证模型有效性 if (!ModelState.IsValid) { return Page(); } // 将主记录与明细批量添加到数据库 _dbContext.EmployeeHeaders.Add(Header); await _dbContext.SaveChangesAsync(); // 保存成功后跳转到员工列表页(可根据需求调整) return RedirectToPage("/Employees/Index"); } }
4. 收尾与验证
- 执行数据库迁移:在包管理器控制台依次运行
Add-Migration InitialEmployeeSchema和Update-Database,生成对应的数据库表。 - 测试功能:启动项目后访问该页面,录入主信息,添加多条明细,提交后查看数据库是否正确保存了主记录和对应的明细。
内容的提问来源于stack exchange,提问作者user21872512
相关产品推荐
相关产品推荐

