如何为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
相关产品推荐
相关产品推荐

