如何将输入字段绑定到两个不同的模型?

How to bind input fields to two different models?

我正在构建图书列表应用程序。它有以下型号:

  1. Book (int Id, string Title, string Type, int MinimumAge) [Id=Key, Title=Required, Type=Required, MinimumAge=Required]
  2. 流派(int Id, string Name) [大师table]
  3. BookGenre (int BookId, int GenreId) [无键实体]
  4. CreateViewModel(书籍书籍,IEnumerable 流派)

现在我正在处理 CREATE 操作。

控制器代码 (BookController.cs)

此控制器的 Create() POST 方法不完整。

using BookList.Data;
using BookList.Models;
using Microsoft.AspNetCore.Mvc;

namespace BookList.Controllers
{
    public class BookController : Controller
    {
        private readonly ApplicationDbContext db;

        public BookController(ApplicationDbContext db)
        {
            this.db = db;
        }

        // READ (Get)
        public IActionResult Index()
        {
            IEnumerable<Book> bookList = db.Books;
            return View(bookList);
        }

        // CREATE (Get)
        public IActionResult Create()
        {
            // Create custom view model 
            CreateViewModel model = new CreateViewModel();
            model.Book = new Book();
            model.Genre = new Genre();

            return View(model);
        }

        // CREATE (Post)
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Create(CreateViewModel obj) 
        {
            // Write your code here
            return RedirectToAction("Index");
        }
    }
}

查看代码 (Create.cshtml):

@model CreateViewModel

<div class="row">
    <div class="border mt-4">
        <div class="row py-2">
            <h2 class="text-primary">Add a New Book</h2>
            <hr />
        </div>

        <form asp-action="Create">
            <div asp-validation-summary="All" class="text-danger"></div>

            @* Title *@
            <div class="form-group mb-2">
                <label asp-for="Book.Title"></label><br />
                <input asp-for="Book.Title" class="form-control" />
                <span asp-validation-for="Book.Title" class="text-danger"></span>
            </div>

            @* Genre *@
            <div class="form-group mb-2">
                <label>Genre</label><br/>
                <div>
                    @foreach(var genre in Model.Genres)
                    {
                        <label for=@genre.Id>@genre.Name</label>
                        <input type="checkbox" id=@genre.Id value=@genre.Name />
                    }
                </div>
            </div>

            @* Type(Fiction/Non-Fiction) *@
            <div class="form-group mb-2">
                <label asp-for="Book.Type"></label><br />
                <div>
                    Fiction <input type="radio" asp-for="Book.Type" value="Fiction" />
                    Non-Fiction <input type="radio" asp-for="Book.Type" value="Non-Fiction"/>
                </div>
                <span asp-validation-for="Book.Type" class="text-danger"></span>
            </div>

            @* Minimum Age(dropdown) *@
            <div class="form-group mb-2">
                <label asp-for="Book.MinimumAge" class="control-label"></label>
                <select asp-for="Book.MinimumAge" class="form-control">
                    <option value=8>8</option>
                    <option value=12>12</option>
                    <option value=16>16</option>
                    <option value=18>18</option>
                </select>
                <span asp-validation-for="Book.MinimumAge" class="text-danger"></span>
            </div>

            <div class="form-group mb-2">
                <input type="submit" value="Add" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@*Cient-side validation scripts*@

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

假设用户输入以下详细信息并单击创建:

Title="XYZ", Genres="Action,Adventure", Type="Fiction", 最低年龄=12

然后,我想 (auto-id, "XYZ","Fiction",12) 进入本书 table。并且,(auto-id,1) 和 (auto-id,2) 进入 BookGenre table.

流派大师 table 包含以下详细信息供您参考。 BookGenre table 是一个 Keyless 实体。

首先要知道模型绑定系统是通过名称属性绑定数据的。

从视图设计中我可以看到您的 CreateViewModel 包含 IEnumerable<Genre> Genres,因此前端应添加如下名称:Genres[index].PropertyName。但是随后你会发现一个问题,如果你要选择不连续的复选框,你只会收到连续的值而错过不连续的。

因此建议您也创建一个 属性 List<string> GenresList 并在前端添加 name="GenresList"

这是一个完整的工作演示:

型号:

public class Book
{
    public int Id { get; set; }
    public int MininumAge { get; set; }
    public string Title { get; set; }        
    public string Type { get; set; }
    public ICollection<Genre>? Genres { get; set; }
}
public class Genre
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Book>? Books { get; set; }
}
public class CreateViewModel
{
    public Book Book { get; set; }
    public List<Genre>? Genres { get; set; }
    public List<string> GenresList { get; set; }
}

查看:

@model CreateViewModel

<div class="row">
    <div class="border mt-4">
        <div class="row py-2">
            <h2 class="text-primary">Add a New Book</h2>
            <hr />
        </div>

        <form asp-action="Create">
            <div asp-validation-summary="All" class="text-danger"></div>

            @* Title *@
            <div class="form-group mb-2">
                <label asp-for="Book.Title"></label><br />
                <input asp-for="Book.Title" class="form-control" />
                <span asp-validation-for="Book.Title" class="text-danger"></span>
            </div>

            @* Genre *@
            <div class="form-group mb-2">
                <label>Genre</label><br/>
                <div>     
                    @foreach(var genre in Model.Genres)
                    {                       
                        <label for=@genre.Id>@genre.Name</label>         @* add name here*@
                        <input type="checkbox" id=@genre.Id value=@genre.Name name="GenresList"/>
                    }
                </div>
            </div>

            @* Type(Fiction/Non-Fiction) *@
            <div class="form-group mb-2">
                <label asp-for="Book.Type"></label><br />
                <div>
                    Fiction <input type="radio" asp-for="Book.Type" value="Fiction" />
                    Non-Fiction <input type="radio" asp-for="Book.Type" value="Non-Fiction"/>
                </div>
                <span asp-validation-for="Book.Type" class="text-danger"></span>
            </div>

            @* Minimum Age(dropdown) *@
            <div class="form-group mb-2">
                <label asp-for="Book.MininumAge" class="control-label"></label>
                <select asp-for="Book.MininumAge" class="form-control">
                    <option value=8>8</option>
                    <option value=12>12</option>
                    <option value=16>16</option>
                    <option value=18>18</option>
                </select>
                <span asp-validation-for="Book.MininumAge" class="text-danger"></span>
            </div>

            <div class="form-group mb-2">
                <input type="submit" value="Add" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

控制器:

public class BooksController : Controller
{
    private readonly MvcProj6_0Context _context;
    public BooksController(MvcProj6_0Context context)
    {
        _context = context;
    }
    // GET: Books/Create
    public IActionResult Create()
    {
        CreateViewModel model = new CreateViewModel();
        model.Book = new Book();
        model.Genres = _context.Genre.ToList();
        return View(model);
    }

    // POST: Books/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(CreateViewModel obj)
    {
        if (ModelState.IsValid)
        {
            var genres = new List<Genre>();
            foreach (var item in obj.GenresList)
            {
                genres.Add(_context.Genre.Where(a => a.Name == item).First());
            }
            obj.Book.Genres = genres;
            _context.Add(obj.Book);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(obj);
    }
}

注:

在 .NET 6 中,它在项目文件中包含 <Nullable>enable</Nullable> 元素,这使得 属性 non-nullable。 non-nullable 属性 必须要有,否则ModelState无效。您可以使用 ? 或在模型设计中初始化 属性 以跳过所需的验证。