ASP.NET Core 搜索选择项模态框实现技术求助
ASP.NET MVC配方配料搜索模态框实现问题
背景
我是ASP.NET及Web开发新手,已掌握MVC控制器与视图的基础用法,成功实现了Recipe(配方)主从表创建视图,可手动添加Ingredient(配料)的Article(产品)ID和数量后保存到数据库。现在需要通过可复用的Partial View(局部视图)产品搜索模态框来添加产品。
场景
视图绑定Recipe模型(包含ICollection
若能不使用JavaScript实现更佳,思路是将包含配料的Recipe传入模态框,在模态框中为Recipe对象添加Article,再重新加载调用方表单(如原有3行配料,选择后变为4行)。
核心需求
- 将选中的Article传递回Recipe创建页面,并显示已添加到配料列表中
- 实现Article的可搜索控件
- 相关必要优化
遇到的问题
- 难以找到非简单增改操作的示例,现有示例均在同一模态框/表单中加载创建新对象所需的全部数据
- 模态框定义在Partial View时,调用按钮无法触发(点击无反应);定义在同一.cshtml文件时可正常显示,但因视图绑定Recipe模型,无法使用Article列表填充下拉框(尝试过自定义ViewModel同时包含Recipe和Articles,但未正确实现)
- 通过ViewBag.Articles传递产品列表,用Select2实现模态框下拉框,但显示为传统下拉框(可能未加载Bootstrap和搜索脚本)
代码说明
实际使用西班牙语命名,已翻译为英文(如Receta改为Recipe);CreateRecipe.js仅包含添加/删除配料行及显示按钮的代码。
模型类代码
Recipe.cs
namespace WebApp.Models { public class Recipe { [Key] public int Id { get; set; } [Required] public string Name { get; set; } public double Result { get; set; } // 单位:千克 public int Time{ get; set; } // 单位:分钟 public virtual List<Ingredient> Ingredients { get; set;} } }
Ingredient.cs(原类名Ingrediente已翻译)
namespace WebApp.Models { public class Ingredient { [Key] public int Id { get; set; } public double Quantity { get; set; } [DisplayName("产品")] [Column("idModels")] public long ArticleId { get; set; } public virtual Article Article { get; set; } public int RecipeId { get; set; } public virtual Recipe Recipe { get; set; } } }
Article.cs
namespace WebApp.Models { [Table("models")] public class Article { [Key] public int idModels { get; set; } [DisplayName("产品")] public string mod_name { get; set; } } }
控制器代码
RecipeController.cs
namespace WebApp.Controllers { public class RecipeController : Controller { private readonly WebAppContext _context; public RecipeController(WebAppContext context) { _context = context; } // 数据库中的配方列表,支持编辑、查看详情和删除 // GET: Recipes public async Task<IActionResult> Index() { return _context.Recipe != null ? View(await _context.Recipe.ToListAsync()) : Problem("实体集 'WebApp.Recipe' 为空。"); } // 进入配方创建视图 // 如果从Index视图跳转,在内存中创建Recipe对象 // 如果携带已有的配方及配料数据,通过模型绑定加载 // GET: Recipe/Create [HttpGet] public IActionResult Create() { Recipe sampleRecipe = new Recipe { Ingredients = new List<Ingredient> { new Ingredient() } }; if (ViewData["NewArticle"] != null) { var newArticle = ViewData["NewArticle"] as Article; sampleRecipe.Ingredients.Add(new Ingredient() { ArticleId = newArticle.idModels, Quantity = 1000 }); } List<Article> listArticles = _context.Article.ToList(); ViewBag.Articles = listArticles; return View(sampleRecipe); } // 加载产品列表到模态框供选择 // GET [HttpGet] public ActionResult ShowModalController(Recipe savedRecipe) { return PartialView("_SearchArticle", savedRecipe); } // 将选中的产品添加到内存中的配方配料列表 // POST [HttpPost] public IActionResult ConfirmArticle(Recipe savedRecipe) { // TODO: 此处需要实现添加逻辑 return View("Create", savedRecipe); } // 保存配方及配料到数据库 // POST: Recipe/Create [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Name,Result,Time, Ingredients")] Recipe recipe) { _context.Add(recipe); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } } }
视图代码
Create.cshtml(原文件命名修正为创建视图规范命名)
@model WebApp.Models.Recipe @{ ViewData["Title"] = "创建配方"; } <div class="col-11 mx-auto pt-0"> <div class="card"> <div class="card-header bg-success text-uppercase text-white"> <h4>添加配方</h4> </div> <form asp-action="Create"> <div class="row"> <div class="col-md-6"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Name" class="control-label"></label> <input asp-for="Name" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Result" class="control-label"></label> <input asp-for="Result" class="form-control" /> <span asp-validation-for="Result" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Time" class="control-label"></label> <input asp-for="Time" class="form-control" /> <span asp-validation-for="Time" class="text-danger"></span> </div> <!-- 配方配料列表 --> <div id="ListIngredients" class="col-md-12"> <table id="TableIngredients" class="table table-striped table-sm"> <thead> <tr> <th scope="col">产品</th> <th scope="col">数量</th> <th scope="col"></th> </tr> </thead> <tbody> @for (int i = 0; i < Model.Ingredients.Count; i++) { <tr> <td> @Html.EditorFor(x => x.Ingredients[i].ArticleId, new { htmlAttributes = new { @class = "form-control" } }) </td> <td> @Html.EditorFor(x => x.Ingredients[i].Quantity, new { htmlAttributes = new { @class = "form-control" } }) </td> <td> <!-- 添加占位按钮,为添加/删除按钮预留空间 --> <!-- 添加@i为行按钮生成唯一ID --> <button id="btndummy-@i" type="button" class="btn btn-sm invisible">占位</button> <button id="AddIngredientButton-@i" onclick="NewLineIngredient(this)" type="button" style="left:45%;position:absolute;" class="btn btn-sm btn-secondary visible"> 添加 </button> <button id="RemoveIngredientButton-@i" onclick="RemoveLineIngredient(this)" type="button" style="left:45%;position:absolute;" class="btn btn-sm btn-danger invisible"> 删除 </button> <button id='chooseArticle-@i' class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalChooseArticle">选择产品</button> </td> </tr> } </tbody> </table> </div> <input type="hidden" id="HiddenLastIndex" value="0" /> <div class="col-12 btn-group"> <div class="form-group col-6 text-right p-2"> <a asp-action="Index" class="btn btn-secondary"> 取消 </a> </div> <div class="form-group col-6 text-left p-2"> <input type="submit" value="确认" class="btn btn-primary" /> </div> </div> </div> </div> </form> </div> </div> <div> <a asp-action="Index">返回列表</a> </div> <div id="modalChooseArticle" class="modal fade" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> @{ Html.RenderPartial("_SearchArticle"); } </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> @section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } <script src="/js/CreateRecipe.js"> </script> }
_SearchArticle.cshtml(局部视图)
<div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">选择产品</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <form asp-action="ConfirmArticle" method="post"> @Html.AntiForgeryToken() <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label class="control-label">产品名称</label> <select class="form-select dropdownListClass" name="SelectedArticleId"> @foreach (var item in ViewBag.Articles) { <option value="@item.idModels">@item.mod_name</option> } </select> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> <button type="submit" class="btn btn-primary">确认选择</button> </div> </form> </div>
解决方案要点
1. 修复模态框与Partial View的调用问题
- 移除模态框按钮上的冗余
data-url,直接在主视图中渲染局部视图,确保Bootstrap模态框初始化逻辑正常 - 在局部视图表单中添加
@Html.AntiForgeryToken(),防止CSRF攻击
2. 实现无JS的产品选择逻辑
修改ConfirmArticle方法,接收选中的产品ID并添加到当前配方的配料列表:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult ConfirmArticle(int SelectedArticleId, [Bind("Id,Name,Result,Time,Ingredients")] Recipe savedRecipe) { var selectedArticle = _context.Article.FirstOrDefault(a => a.idModels == SelectedArticleId); if (selectedArticle != null) { savedRecipe.Ingredients.Add(new Ingredient { ArticleId = selectedArticle.idModels, Quantity = 1000 // 默认数量,可后续优化为用户输入 }); } ViewBag.Articles = _context.Article.ToList(); return View("Create", savedRecipe); }
3. 实现Select2可搜索下拉框
- 确保项目中已引入jQuery、Bootstrap和Select2的CSS与JS文件
- 在主视图的
Scripts区域添加初始化代码:
<script> $(document).ready(function() { $('.dropdownListClass').select2({ placeholder: "搜索产品", allowClear: true }); }); </script>
4. 优化ViewModel(可选,更规范的做法)
创建包含Recipe和Articles的ViewModel,避免使用ViewBag传递数据:
public class RecipeCreateViewModel { public Recipe Recipe { get; set; } public List<Article> Articles { get; set; } }
修改Create方法返回该ViewModel,并在视图中绑定Model.Recipe,用Model.Articles填充下拉框。
内容的提问来源于stack exchange,提问作者Jonathan Cumini
相关产品推荐
相关产品推荐

