在消费Web API的MVC应用中实现下拉复选列表填充
MVC创建页多选作者/角色下拉复选框实现方案
问题场景
开发了一个通过REST方式消费Web API的简易MVC应用,MVC端控制器通过发起HTTP请求调用Web API,结合Razor语法渲染填充视图。需要在Create(创建)动作对应的页面中实现下拉多选功能,支持选择数据库中已存在的作者、角色,当前页面为脚手架自动生成,未实现该多选能力。
现有基础代码
初始Create视图代码
@model ComicBookInventory.Shared.ComicBookWithAuthorsAndCharactersViewModel @{ ViewData["Title"] = "CreateComicBook"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h1>CreateComicBook</h1> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="CreateComicBook"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Id" class="control-label"></label> <input asp-for="Id" class="form-control" /> <span asp-validation-for="Id" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Title" class="control-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Description" class="control-label"></label> <input asp-for="Description" class="form-control" /> <span asp-validation-for="Description" class="text-danger"></span> </div> <div class="form-group form-check"> <label class="form-check-label"> <input class="form-check-input" asp-for="IsRead" /> @Html.DisplayNameFor(model => model.IsRead) </label> </div> <div class="form-group"> <label asp-for="DateRead" class="control-label"></label> <input asp-for="DateRead" class="form-control" /> <span asp-validation-for="DateRead" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Rating" class="control-label"></label> <input asp-for="Rating" class="form-control" /> <span asp-validation-for="Rating" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Genre" class="control-label"></label> <input asp-for="Genre" class="form-control" /> <span asp-validation-for="Genre" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="CoverUrl" class="control-label"></label> <input asp-for="CoverUrl" class="form-control" /> <span asp-validation-for="CoverUrl" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } }
初始Create提交动作代码
public async Task<IActionResult> CreateComicBook(ComicBookWithAuthorsAndCharactersViewModel model) { string uri = $"https://localhost:5001/api/comicbook/add-book/"; HttpClient client = _httpClientFactory.CreateClient( name: "ComicBookInventory.Api"); var postTask = await client.PostAsJsonAsync<ComicBookWithAuthorsAndCharactersViewModel>(uri, model); if (postTask.IsSuccessStatusCode) { return RedirectToAction("GetAllComics"); } else { return View(model); } }
初始视图模型定义
namespace ComicBookInventory.Shared { public class ComicBookWithAuthorsAndCharactersViewModel { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public bool IsRead { get; set; } public DateTime? DateRead { get; set; } public int? Rating { get; set; } public string Genre { get; set; } public string? CoverUrl { get; set; } /// <summary> /// Navigation properties /// </summary> /// a book can have many authors public ICollection<string>? AuthorNames { get; set; } public ICollection<string>? CharacterNames { get; set; } } }
分步实现方案
1. 调整视图模型,补充多选所需字段
多选控件需要两类数据:全量可选项、用户选中的值,给视图模型补充对应属性:
using Microsoft.AspNetCore.Mvc.Rendering; namespace ComicBookInventory.Shared { public class ComicBookWithAuthorsAndCharactersViewModel { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public bool IsRead { get; set; } public DateTime? DateRead { get; set; } public int? Rating { get; set; } public string Genre { get; set; } public string? CoverUrl { get; set; } // 存储用户提交时选中的作者、角色 public ICollection<string>? SelectedAuthors { get; set; } public ICollection<string>? SelectedCharacters { get; set; } // 存储全量可选作者、角色,供下拉渲染 public IEnumerable<SelectListItem>? AuthorOptions { get; set; } public IEnumerable<SelectListItem>? CharacterOptions { get; set; } // 保留原有字段适配Web API入参要求 public ICollection<string>? AuthorNames { get; set; } public ICollection<string>? CharacterNames { get; set; } } }
提示:创建场景下Id一般为数据库自增主键,不需要用户输入,可以删掉视图中Id对应的表单项,避免不必要的校验问题
2. 补充GET请求的页面加载动作
当前仅实现了POST提交逻辑,需要补充页面首次加载时的动作,调用Web API拉取全量作者、角色数据,组装成下拉选项传给视图:
[HttpGet] public async Task<IActionResult> CreateComicBook() { var client = _httpClientFactory.CreateClient("ComicBookInventory.Api"); var model = new ComicBookWithAuthorsAndCharactersViewModel(); // 替换为你自己的Web API获取全量作者、角色的接口地址 var authors = await client.GetFromJsonAsync<List<string>>("https://localhost:5001/api/authors/all"); var characters = await client.GetFromJsonAsync<List<string>>("https://localhost:5001/api/characters/all"); // 组装下拉选项 model.AuthorOptions = authors?.Select(a => new SelectListItem { Text = a, Value = a }) ?? Enumerable.Empty<SelectListItem>(); model.CharacterOptions = characters?.Select(c => new SelectListItem { Text = c, Value = c }) ?? Enumerable.Empty<SelectListItem>(); return View(model); }
如果你的Web API返回的作者/角色是带ID的实体对象,把Value绑定为ID、Text绑定为名称即可,对应把SelectedAuthors/SelectedCharacters的类型改为ID类型(如int)即可。
3. 修改POST提交逻辑,处理校验失败场景
提交失败返回视图时,必须重新填充下拉选项,否则控件会显示为空:
[HttpPost] public async Task<IActionResult> CreateComicBook(ComicBookWithAuthorsAndCharactersViewModel model) { string uri = $"https://localhost:5001/api/comicbook/add-book/"; HttpClient client = _httpClientFactory.CreateClient(name: "ComicBookInventory.Api"); // 把用户选中的值映射为原有API需要的字段 model.AuthorNames = model.SelectedAuthors; model.CharacterNames = model.SelectedCharacters; var response = await client.PostAsJsonAsync<ComicBookWithAuthorsAndCharactersViewModel>(uri, model); if (response.IsSuccessStatusCode) { return RedirectToAction("GetAllComics"); } else { // 重新拉取选项填充模型 var authors = await client.GetFromJsonAsync<List<string>>("https://localhost:5001/api/authors/all"); var characters = await client.GetFromJsonAsync<List<string>>("https://localhost:5001/api/characters/all"); model.AuthorOptions = authors?.Select(a => new SelectListItem { Text = a, Value = a }) ?? Enumerable.Empty<SelectListItem>(); model.CharacterOptions = characters?.Select(c => new SelectListItem { Text = c, Value = c }) ?? Enumerable.Empty<SelectListItem>(); return View(model); } }
4. 修改视图,添加多选控件
在原有表单的CoverUrl表单项后面、提交按钮前面,添加两个多选下拉框。原生<select multiple>按住Ctrl即可多选,设置size属性控制可见行数,也可以引入bootstrap-multiselect、select2等前端插件优化成带复选框、搜索功能的下拉样式,核心绑定逻辑不变:
<div class="form-group"> <label asp-for="SelectedAuthors" class="control-label">作者</label> <select asp-for="SelectedAuthors" asp-items="Model.AuthorOptions" class="form-control" multiple size="5"></select> <span asp-validation-for="SelectedAuthors" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="SelectedCharacters" class="control-label">角色</label> <select asp-for="SelectedCharacters" asp-items="Model.CharacterOptions" class="form-control" multiple size="5"></select> <span asp-validation-for="SelectedCharacters" class="text-danger"></span> </div>
5. EF Core多对多关系配置(Web API端)
如果Web API端还未配置漫画与作者、角色的多对多关系,EF Core 5.0+版本配置非常简单:
- 给Author、Character实体类添加
ICollection<ComicBook> ComicBooks导航属性 - 在DbContext的
OnModelCreating方法中添加映射配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置漫画-作者多对多 modelBuilder.Entity<ComicBook>() .HasMany(c => c.Authors) .WithMany(a => a.ComicBooks); // 配置漫画-角色多对多 modelBuilder.Entity<ComicBook>() .HasMany(c => c.Characters) .WithMany(ch => ch.ComicBooks); }
配置完成后执行Add-Migration、Update-Database命令,EF Core会自动生成中间关联表,提交时直接把传入的作者/角色集合绑定到漫画实体的导航属性,保存即可自动维护关联关系。
内容的提问来源于stack exchange,提问作者RNemeth
相关产品推荐
相关产品推荐

