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

在消费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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 21:24:26