ASP.NET Core 6.0 Razor页面:如何在项目页同步编辑1:n关联干系人
在ASP.NET Core Razor页面中实现项目与干系人同页创建/编辑
1. 确认模型与EF配置
首先确保实体类和EF关系配置正确:
// Project实体 public class Project { public int Id { get; set; } public string Name { get; set; } // 其他项目属性... public ICollection<Stakeholder> Stakeholders { get; set; } = new List<Stakeholder>(); } // Stakeholder实体 public class Stakeholder { public int Id { get; set; } public string Name { get; set; } public string Role { get; set; } public string ContactEmail { get; set; } // 其他干系人属性... public int ProjectId { get; set; } public Project Project { get; set; } }
如果需要显式配置关系,在DbContext的OnModelCreating中添加:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Project>() .HasMany(p => p.Stakeholders) .WithOne(s => s.Project) .HasForeignKey(s => s.ProjectId) .OnDelete(DeleteBehavior.Cascade); }
2. 配置PageModel
创建页(Create.cshtml.cs)
初始化干系人集合并处理提交:
public class CreateModel : PageModel { private readonly YourDbContext _context; public CreateModel(YourDbContext context) { _context = context; } [BindProperty] public Project Project { get; set; } = new Project(); public IActionResult OnGet() { // 初始化一个空干系人,方便用户直接输入 Project.Stakeholders.Add(new Stakeholder()); return Page(); } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } // 移除未填写的空干系人项 Project.Stakeholders = Project.Stakeholders.Where(s => !string.IsNullOrWhiteSpace(s.Name)).ToList(); _context.Projects.Add(Project); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } }
编辑页(Edit.cshtml.cs)
加载现有项目及干系人,处理增删改逻辑:
public class EditModel : PageModel { private readonly YourDbContext _context; public EditModel(YourDbContext context) { _context = context; } [BindProperty] public Project Project { get; set; } public async Task<IActionResult> OnGetAsync(int? id) { if (id == null) { return NotFound(); } Project = await _context.Projects .Include(p => p.Stakeholders) .FirstOrDefaultAsync(m => m.Id == id); if (Project == null) { return NotFound(); } // 无干系人时添加空项 if (!Project.Stakeholders.Any()) { Project.Stakeholders.Add(new Stakeholder()); } return Page(); } public async Task<IActionResult> OnPostAsync(int id) { var projectToUpdate = await _context.Projects .Include(p => p.Stakeholders) .FirstOrDefaultAsync(p => p.Id == id); if (projectToUpdate == null) { return NotFound(); } // 更新项目基本属性 if (await TryUpdateModelAsync<Project>( projectToUpdate, "Project", p => p.Name /* 其他项目属性 */)) { var submittedStakeholders = Project.Stakeholders.Where(s => !string.IsNullOrWhiteSpace(s.Name)).ToList(); // 删除已移除的干系人 foreach (var existingStakeholder in projectToUpdate.Stakeholders.ToList()) { if (!submittedStakeholders.Any(s => s.Id == existingStakeholder.Id)) { _context.Stakeholders.Remove(existingStakeholder); } } // 添加或更新干系人 foreach (var submittedStakeholder in submittedStakeholders) { var existingStakeholder = projectToUpdate.Stakeholders.FirstOrDefault(s => s.Id == submittedStakeholder.Id); if (existingStakeholder != null) { _context.Entry(existingStakeholder).CurrentValues.SetValues(submittedStakeholder); } else { projectToUpdate.Stakeholders.Add(submittedStakeholder); } } await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } return Page(); } }
3. 页面表单实现
在Create.cshtml或Edit.cshtml中渲染动态表单:
@page @model YourNamespace.Pages.Projects.CreateModel <h1>Create Project</h1> <form method="post"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Project.Name" class="control-label"></label> <input asp-for="Project.Name" class="form-control" /> <span asp-validation-for="Project.Name" class="text-danger"></span> </div> <!-- 其他项目属性字段 --> <!-- 干系人区域 --> <h3>干系人</h3> <div id="stakeholders-container"> @for (int i = 0; i < Model.Project.Stakeholders.Count; i++) { <div class="stakeholder-item mb-3 p-3 border"> <input type="hidden" asp-for="Project.Stakeholders[i].Id" /> <div class="row"> <div class="col-md-4"> <label asp-for="Project.Stakeholders[i].Name" class="control-label"></label> <input asp-for="Project.Stakeholders[i].Name" class="form-control" /> <span asp-validation-for="Project.Stakeholders[i].Name" class="text-danger"></span> </div> <div class="col-md-3"> <label asp-for="Project.Stakeholders[i].Role" class="control-label"></label> <input asp-for="Project.Stakeholders[i].Role" class="form-control" /> </div> <div class="col-md-4"> <label asp-for="Project.Stakeholders[i].ContactEmail" class="control-label"></label> <input asp-for="Project.Stakeholders[i].ContactEmail" class="form-control" /> <span asp-validation-for="Project.Stakeholders[i].ContactEmail" class="text-danger"></span> </div> <div class="col-md-1 align-self-end"> <button type="button" class="btn btn-danger remove-stakeholder">删除</button> </div> </div> </div> } </div> <button type="button" id="add-stakeholder" class="btn btn-primary mb-3">添加干系人</button> <div class="form-group"> <input type="submit" value="创建" class="btn btn-primary" /> </div> </form> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} <script> // 添加干系人逻辑 document.getElementById('add-stakeholder').addEventListener('click', function () { const container = document.getElementById('stakeholders-container'); const lastItem = container.querySelector('.stakeholder-item:last-child'); if (!lastItem) return; const newItem = lastItem.cloneNode(true); // 清空输入框与验证提示 newItem.querySelectorAll('input:not([type="hidden"])').forEach(input => input.value = ''); newItem.querySelector('input[type="hidden"]').value = '0'; newItem.querySelectorAll('.text-danger').forEach(span => span.textContent = ''); // 更新索引值 const currentIndex = parseInt(lastItem.querySelector('input[name^="Project.Stakeholders"]').name.match(/\[(\d+)\]/)[1]); const newIndex = currentIndex + 1; newItem.querySelectorAll('[asp-for]').forEach(element => { const oldFor = element.getAttribute('asp-for'); const newFor = oldFor.replace(`[${currentIndex}]`, `[${newIndex}]`); element.setAttribute('asp-for', newFor); if (element.tagName === 'INPUT') { element.name = newFor; element.id = newFor.replace('.', '_'); } else if (element.tagName === 'LABEL') { element.htmlFor = newFor.replace('.', '_'); } }); container.appendChild(newItem); }); // 删除干系人逻辑 document.addEventListener('click', function (e) { if (e.target.classList.contains('remove-stakeholder')) { const item = e.target.closest('.stakeholder-item'); const container = document.getElementById('stakeholders-container'); if (container.children.length > 1) { item.remove(); // 重新整理索引(可选,模型绑定允许不连续) Array.from(container.children).forEach((item, index) => { item.querySelectorAll('[asp-for]').forEach(element => { const oldFor = element.getAttribute('asp-for'); const newFor = oldFor.replace(/\[(\d+)\]/, `[${index}]`); element.setAttribute('asp-for', newFor); if (element.tagName === 'INPUT') { element.name = newFor; element.id = newFor.replace('.', '_'); } else if (element.tagName === 'LABEL') { element.htmlFor = newFor.replace('.', '_'); } }); }); } } }); </script> }
关键说明
- 模型绑定依赖
name属性格式:Project.Stakeholders[{index}].PropertyName,通过asp-for标签助手自动生成,确保后端正确接收集合。 - JS克隆元素时必须更新索引值,避免重复
name属性导致绑定失败。 - 编辑页需对比提交集合与数据库现有集合,标记EF实体状态以处理增删改。
内容的提问来源于stack exchange,提问作者Anatoli
相关产品推荐
相关产品推荐

