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

ASP.NET Core 搜索选择项模态框实现技术求助

ASP.NET MVC配方配料搜索模态框实现问题

背景

我是ASP.NET及Web开发新手,已掌握MVC控制器与视图的基础用法,成功实现了Recipe(配方)主从表创建视图,可手动添加Ingredient(配料)的Article(产品)ID和数量后保存到数据库。现在需要通过可复用的Partial View(局部视图)产品搜索模态框来添加产品。

场景

视图绑定Recipe模型(包含ICollection),点击配料行的「搜索产品」按钮时,需弹出带有搜索条件的模态框(至少需要类似Select2的可搜索输入框或下拉框来查找Article),选择产品后将其添加到对应配料行。

若能不使用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">&nbsp;取消&nbsp;</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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 15:21:59