You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:53:14