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

如何为Razor表单动态添加Food元素并实现客户端验证与绑定

动态添加表单元素并绑定列表属性,同时启用客户端验证

需求说明

需要在表单中实现点击按钮动态添加一行内容,每行包含两个元素:

  • 食物类型下拉选择框
  • 数量输入框
    提交表单时,这些动态添加的元素要正确绑定到后端FoodItems列表属性,同时要启用默认的jQuery Validation和Unobtrusive Validation客户端验证。

后端类定义

public class Food {
    public int SelectedFoodItemId {get; set;}
    public int Amount {get; set;}
}

Razor页面代码

[BindProperty]
public InputModel Input {get; set;}

public class InputModel{
    public List<Food> FoodItems {get; set;}
    public List<SelectListItem> FoodItemsSelectList {get; set;}
    
    // 其他需展示的属性已省略
}

当前方案痛点

之前用JavaScript实现时,需要通过计数器跟踪行数,Ajax获取下拉列表JSON数据生成新行,还要手动修改元素的Id和Name字段来实现模型绑定和重新启用验证。该方案可行但工作量大,希望找到更简便的实现方式。


简便实现方案

1. 用Razor模板生成基础行结构

在表单中添加一个隐藏的模板行,利用Razor的标签助手自动生成正确的绑定名称和验证属性,避免手动拼接HTML和验证规则:

<!-- 隐藏的模板行,用于克隆生成新行 -->
<div id="foodItemTemplate" style="display: none;">
    <div class="food-item-row">
        <select asp-for="FoodItems[0].SelectedFoodItemId" asp-items="Model.Input.FoodItemsSelectList" class="form-control"></select>
        <span asp-validation-for="FoodItems[0].SelectedFoodItemId" class="text-danger"></span>
        
        <input asp-for="FoodItems[0].Amount" type="number" class="form-control" />
        <span asp-validation-for="FoodItems[0].Amount" class="text-danger"></span>
        
        <button type="button" class="btn btn-danger remove-row">删除</button>
    </div>
</div>

<!-- 存放动态行的容器 -->
<div id="foodItemsContainer">
    <!-- 渲染初始行(如果没有默认数据则显示一行空行) -->
    @if (Model.Input.FoodItems == null || Model.Input.FoodItems.Count == 0)
    {
        <div class="food-item-row">
            <select asp-for="FoodItems[0].SelectedFoodItemId" asp-items="Model.Input.FoodItemsSelectList" class="form-control"></select>
            <span asp-validation-for="FoodItems[0].SelectedFoodItemId" class="text-danger"></span>
            
            <input asp-for="FoodItems[0].Amount" type="number" class="form-control" />
            <span asp-validation-for="FoodItems[0].Amount" class="text-danger"></span>
            
            <button type="button" class="btn btn-danger remove-row">删除</button>
        </div>
    }
    else
    {
        @for (int i = 0; i < Model.Input.FoodItems.Count; i++)
        {
            <div class="food-item-row">
                <select asp-for="FoodItems[i].SelectedFoodItemId" asp-items="Model.Input.FoodItemsSelectList" class="form-control"></select>
                <span asp-validation-for="FoodItems[i].SelectedFoodItemId" class="text-danger"></span>
                
                <input asp-for="FoodItems[i].Amount" type="number" class="form-control" />
                <span asp-validation-for="FoodItems[i].Amount" class="text-danger"></span>
                
                <button type="button" class="btn btn-danger remove-row">删除</button>
            </div>
        }
    }
</div>

<button type="button" id="addFoodItemBtn" class="btn btn-primary mt-2">添加食物项</button>

2. JavaScript处理动态添加、删除与验证

通过克隆模板行替换索引值的方式生成新行,同时重新解析Unobtrusive验证规则,确保动态元素能被正确验证:

$(document).ready(function() {
    // 添加按钮点击事件
    $('#addFoodItemBtn').click(function() {
        const rowCount = $('.food-item-row').length;
        // 克隆模板行
        const newRow = $('#foodItemTemplate .food-item-row').clone();
        
        // 替换模板中的索引标记
        newRow.find('[name]').each(function() {
            const newName = $(this).attr('name').replace('FoodItems[0]', `FoodItems[${rowCount}]`);
            $(this).attr('name', newName);
        });
        newRow.find('[id]').each(function() {
            const newId = $(this).attr('id').replace('FoodItems_0_', `FoodItems_${rowCount}_`);
            $(this).attr('id', newId);
        });
        newRow.find('[data-valmsg-for]').each(function() {
            const newFor = $(this).attr('data-valmsg-for').replace('FoodItems[0]', `FoodItems[${rowCount}]`);
            $(this).attr('data-valmsg-for', newFor);
        });
        
        // 清空新行的输入值
        newRow.find('select').val('');
        newRow.find('input').val('');
        
        // 添加到容器
        $('#foodItemsContainer').append(newRow);
        
        // 重新解析验证规则
        $.validator.unobtrusive.parse(newRow);
        // 绑定删除按钮事件
        bindRemoveRowEvent();
    });
    
    // 绑定删除按钮事件
    function bindRemoveRowEvent() {
        $('.remove-row').off('click').click(function() {
            $(this).closest('.food-item-row').remove();
            // 删除后重新更新所有行的索引,确保绑定正确
            reindexRows();
        });
    }
    
    // 重新索引所有行,解决删除行后的绑定问题
    function reindexRows() {
        $('.food-item-row').each(function(index) {
            $(this).find('[name]').each(function() {
                const newName = $(this).attr('name').replace(/FoodItems\[\d+\]/, `FoodItems[${index}]`);
                $(this).attr('name', newName);
            });
            $(this).find('[id]').each(function() {
                const newId = $(this).attr('id').replace(/FoodItems_\d+_/, `FoodItems_${index}_`);
                $(this).attr('id', newId);
            });
            $(this).find('[data-valmsg-for]').each(function() {
                const newFor = $(this).attr('data-valmsg-for').replace(/FoodItems\[\d+\]/, `FoodItems[${index}]`);
                $(this).attr('data-valmsg-for', newFor);
            });
        });
        // 重新解析整个容器的验证规则
        $.validator.unobtrusive.parse('#foodItemsContainer');
    }
    
    // 初始化删除按钮事件
    bindRemoveRowEvent();
});

3. 后端验证配置

确保项目中已启用Unobtrusive验证,在Program.cs中配置:

builder.Services.AddRazorPages()
    .AddMvcOptions(options => {
        // 可自定义验证提示信息
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "此字段不能为空。");
    })
    .AddDataAnnotationsLocalization();

// 确保中间件顺序正确
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();

方案优势

  • 利用Razor标签助手自动生成绑定名称和验证属性,无需手动拼接复杂的HTML和验证规则
  • 模板克隆的方式大幅减少JS代码复杂度,避免手动构造元素
  • 重新索引逻辑确保删除行后,列表的模型绑定依然正确
  • 自动重新解析Unobtrusive验证,动态元素能继承原有验证规则

内容的提问来源于stack exchange,提问作者Nikola

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 11:42:13