使用来自 select 元素 ASP.NET Core MVC 的属性保存 parent 项和 child 项
Saving parent item and child items with properties from select element ASP.NET Core MVC
在我正在处理的 ASP.NET 核心 MVC 网络应用程序中,我遇到了一个问题,我将通过以下示例进行说明。
假设用户必须保存图书及其标题和页数,以及一位或多位作者。对于每个作者,用户应该从下拉列表中选择他的姓名和角色。
模型有四个 类:
public class Author
{
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public Author(int id, string name)
{
AuthorID = id;
AuthorName = name;
}
}
public class AuthorRole
{
public int AuthorRoleID { get; set; }
public string AuthorRoleName { get; set; }
public AuthorRole(int id, string name)
{
AuthorRoleID = id;
AuthorRoleName = name;
}
}
public class BookAuthor
{
public int? BookAuthorID { get; set; }
public int? BookID { get; set; }
[DisplayName("Name")]
public int? AuthorID { get; set; }
[DisplayName("Role")]
public int? AuthorRoleID { get; set; }
}
public class BookViewModel
{
public int? BookID { get; set; }
[DisplayName("Title")]
public string Title { get; set; }
[DisplayName("Number of pages")]
public int NumberOfPages { get; set; }
public SelectList AuthorsList { get; set; }
public SelectList AuthorRolesList { get; set; }
[DisplayName("Authors")]
public List<BookAuthor> BookAuthors { get; set; }
public BookViewModel()
{
BookAuthors = new List<BookAuthor>();
}
}
表单初始化动作
[HttpGet]
public async Task<IActionResult> AddBook()
{
BookViewModel book = new BookViewModel();
PopulateDropdowns(book);
book.BookAuthors.Add(new BookAuthor());
return View(book);
}
private void PopulateDropdowns(BookViewModel book)
{
List<Author> authors = new List<Author>();
authors.Add(new Author(1, "Morgan Beaman"));
authors.Add(new Author(2, "Cleveland Lemmer"));
authors.Add(new Author(3, "Joanie Wann"));
authors.Add(new Author(4, "Emil Shupp"));
authors.Add(new Author(5, "Zenia Witts"));
book.AuthorsList = new SelectList(authors, "AuthorID", "AuthorName");
List<AuthorRole> authorRoles = new List<AuthorRole>();
authorRoles.Add(new AuthorRole(1, "Author"));
authorRoles.Add(new AuthorRole(2, "Editor"));
authorRoles.Add(new AuthorRole(3, "Translator"));
book.AuthorRolesList = new SelectList(authorRoles, "AuthorRoleID", "AuthorRoleName");
}
returns 观点:
@{
Layout = "_DetailsLayout";
}
@model Test.BookViewModel
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-sm-8 col-lg-6">
<form asp-action="SaveBook" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="BookID" />
<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="NumberOfPages" class="control-label"></label>
<input asp-for="NumberOfPages" class="form-control" />
<span asp-validation-for="NumberOfPages" class="text-danger"></span>
</div>
<label asp-for="BookAuthors" class="control-label"></label>
@await Html.PartialAsync("_AuthorDetails", Model)
<div id="AuthorDetailsWrapper"></div>
<div class="clearfix">
<a class="btn btn-link text-success btn-sm float-right" data-ajax="true"
data-ajax-url="@Url.Action("AddAuthor", new { id = Model.BookID })"
data-ajax-method="GET" data-ajax-mode="after"
data-ajax-loading-duration="400"
data-ajax-update="#AuthorDetailsWrapper" href="">ADD AUTHOR</a>
</div>
<div class="row mt-5">
<div class="col">
<a asp-action="Index" class="btn btn-outline-secondary block-on-mobile mb-2">Back</a>
</div>
<div class="col">
<button type="submit" class="btn btn-success float-right block-on-mobile">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
如果图书有多位作者,用户应单击调用操作的“添加作者”
[HttpGet]
public IActionResult AddAuthor(int? bookid)
{
BookViewModel book = new BookViewModel();
BookAuthor newAuthor = new BookAuthor();
if (bookid != null)
{
newAuthor.BookID = bookid;
}
PopulateDropdowns(book);
book.BookAuthors.Add(newAuthor);
return PartialView("_AuthorDetails", book);
}
returns 观点:
@model Test.BookViewModel
@for (var i = 0; i < Model.BookAuthors.Count; i++)
{
string guid = Guid.NewGuid().ToString();
<div class="form-row">
<input type="hidden" name="BookAuthors.Index" value="@guid" />
<input asp-for="@Model.BookAuthors[i].BookID" type="hidden" data-guid="@guid" />
<input asp-for="@Model.BookAuthors[i].BookAuthorID" type="hidden" data-guid="@guid" />
<div class="form-group col">
<label asp-for="@Model.BookAuthors[i].AuthorID"></label>
<select asp-for="@Model.BookAuthors[i].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="@Model.BookAuthors[i].AuthorID" class="text-danger" data-guid="@guid"></span>
</div>
<div class="form-group col">
<label asp-for="@Model.BookAuthors[i].AuthorRoleID"></label>
<select asp-for="@Model.BookAuthors[i].AuthorRoleID" asp-items="Model.AuthorRolesList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="@Model.BookAuthors[i].AuthorRoleID" data-guid="@guid" class="text-danger"></span>
</div>
</div>
}
在提交表单时,应将模型传递给操作:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveBook(BookViewModel book)
{
// validation and saving book to database
}
所有属性 - 标题和页数,还有完整的作者列表,每个都有 AuthorID 和 AuthorRoleID。
问题是作者的属性(为下拉列表选择的 AuthorID 和 AuthorRoleID)未被传递,即具有空值。
例如,如果我这样填写表格
Filled form, with two authors
对于列表 BookAuthors 的两个元素,预置 AuthorID 和 AuthorRoleID 为空:
Data not passed to action
我认为出现问题是因为 AuthorID(和 AuthorRoleID)的“asp-for”标签助手为 select 元素的“id”和“name”属性生成了错误的值 - 它使用 0作为索引,而不是指导。所以而不是
<select data-guid="283a0dea-9d64-432f-a03d-8c1a167b062a" class="dropdowns" id="BookAuthors_283a0dea-9d64-432f-a03d-8c1a167b062a__AuthorID" name="BookAuthors[283a0dea-9d64-432f-a03d-8c1a167b062a].AuthorID">
<option></option>
<option value="1">Morgan Beaman</option>
<option value="2">Cleveland Lemmer</option>
<option value="3">Joanie Wann</option>
<option value="4">Emil Shupp</option>
<option value="5">Zenia Witts</option>
</select>
元素看起来像这样
<select data-guid="283a0dea-9d64-432f-a03d-8c1a167b062a" class="dropdowns" id="BookAuthors_0__AuthorID" name="BookAuthors[0].AuthorID">
<option></option>
<option value="1">Morgan Beaman</option>
<option value="2">Cleveland Lemmer</option>
<option value="3">Joanie Wann</option>
<option value="4">Emil Shupp</option>
<option value="5">Zenia Witts</option>
</select>
如果我在提交前在 DevTools 中编辑此元素(我用 guid 值替换零),所有内容都会按预期提交。
让我感到困惑的是,如果 AuthorID 不是 select 元素,而是 input 元素,则相同的代码仍然有效。例如,在 _AuthorDetails.cshtml 部分视图中,如果我替换
<select asp-for="@Model.BookAuthors[i].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
和
<input asp-for="@Model.BookAuthors[i].AuthorID" data-guid="@guid" />
生成的元素是:
<input data-guid="926eb580-d52f-4132-ab43-b8fbb63e3d2c" type="number" id="BookAuthors_926eb580-d52f-4132-ab43-b8fbb63e3d2c__AuthorID" name="BookAuthors[926eb580-d52f-4132-ab43-b8fbb63e3d2c].AuthorID" value="" minlength="0">
如果我手动输入作者 ID 并提交表单,数据将通过:
Filled part of form, 5 is entered for author's id
Data passed to action
那么,我该如何修正这段代码,以便生成具有正确索引的 select 元素?
此外,替代解决方案和一般改进技巧也很受欢迎。可能的替代解决方案应该允许在同一页面上添加(以及稍后编辑,使用相同的视图)具有任意数量作者的书籍...
if I fill the form like this Filled form, with two authors for both elements of list BookAuthors, preoperties AuthorID and AuthorRoleID are null
那是因为你的partial view包含了一个input元素,它不是BookAuthors中的属性,而你定义为BookAuthors.Index
,模型绑定系统会误解它:
<input type="hidden" name="BookAuthors.Index" value="@guid" />
要解决此问题,只需更改如下名称,避免使用 BookAuthors.xxx
或 BookAuthors[i].xxx
:
这样的名称
<input type="hidden" name="data" value="@guid" />
更新:
无法将列表传递给后端的原因是每次渲染局部视图时,BookAuthors 的索引总是 0.You 需要渲染 html 如下所示:
一个简单的解决方法是设置一个 属性 来记录 BookAuthors.Change 您的代码的索引,如下所示:
型号:
public class BookViewModel
{
//more properties...
//add Index property...
public int Index { get; set; }
[DisplayName("Authors")]
public List<BookAuthor> BookAuthors { get; set; }
public BookViewModel()
{
BookAuthors = new List<BookAuthor>();
}
}
_AuthorDetails.cshtml:
点击添加作者按钮时不要使用标签helper.Because,索引将更改为1,标签助手不允许列表计数来自1.Use 姓名属性而不是标签助手。
@model BookViewModel
@{
string guid = Guid.NewGuid().ToString();
}
<div class="form-row">
<input type="hidden" name="Index" value="@guid" />
<input name="BookAuthors[@Model.Index].BookID" type="hidden" data-guid="@guid" />
<input name="BookAuthors[@Model.Index].BookAuthorID" type="hidden" data-guid="@guid" />
<div class="form-group col">
<label asp-for="BookAuthors[Model.Index].AuthorID"></label>
<select name="BookAuthors[@Model.Index].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="BookAuthors[Model.Index].AuthorID" class="text-danger" data-guid="@guid"></span>
</div>
<div class="form-group col">
<label asp-for="BookAuthors[Model.Index].AuthorRoleID"></label>
<select name="BookAuthors[@Model.Index].AuthorRoleID" asp-items="Model.AuthorRolesList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="BookAuthors[Model.Index].AuthorRoleID" data-guid="@guid" class="text-danger"></span>
</div>
</div>
控制器:
[HttpGet]
public IActionResult AddAuthor(int? bookid)
{
var index = HttpContext.Session.GetInt32("item").Value;
BookViewModel book = new BookViewModel();
BookAuthor newAuthor = new BookAuthor();
if (bookid != null)
{
newAuthor.BookID = bookid;
}
PopulateDropdowns(book);
book.BookAuthors.Add(newAuthor);
book.Index= index+1;
HttpContext.Session.SetInt32("item", book.Index);
return PartialView("_AuthorDetails", book);
}
public IActionResult AddBook()
{
BookViewModel book = new BookViewModel();
PopulateDropdowns(book);
book.BookAuthors.Add(new BookAuthor());
book.Index = 0;
HttpContext.Session.SetInt32("item", book.Index);
return View(book);
}
确保在 Startup.cs:
中注册会话
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
//....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
//...
}
结果:
在我正在处理的 ASP.NET 核心 MVC 网络应用程序中,我遇到了一个问题,我将通过以下示例进行说明。
假设用户必须保存图书及其标题和页数,以及一位或多位作者。对于每个作者,用户应该从下拉列表中选择他的姓名和角色。
模型有四个 类:
public class Author
{
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public Author(int id, string name)
{
AuthorID = id;
AuthorName = name;
}
}
public class AuthorRole
{
public int AuthorRoleID { get; set; }
public string AuthorRoleName { get; set; }
public AuthorRole(int id, string name)
{
AuthorRoleID = id;
AuthorRoleName = name;
}
}
public class BookAuthor
{
public int? BookAuthorID { get; set; }
public int? BookID { get; set; }
[DisplayName("Name")]
public int? AuthorID { get; set; }
[DisplayName("Role")]
public int? AuthorRoleID { get; set; }
}
public class BookViewModel
{
public int? BookID { get; set; }
[DisplayName("Title")]
public string Title { get; set; }
[DisplayName("Number of pages")]
public int NumberOfPages { get; set; }
public SelectList AuthorsList { get; set; }
public SelectList AuthorRolesList { get; set; }
[DisplayName("Authors")]
public List<BookAuthor> BookAuthors { get; set; }
public BookViewModel()
{
BookAuthors = new List<BookAuthor>();
}
}
表单初始化动作
[HttpGet]
public async Task<IActionResult> AddBook()
{
BookViewModel book = new BookViewModel();
PopulateDropdowns(book);
book.BookAuthors.Add(new BookAuthor());
return View(book);
}
private void PopulateDropdowns(BookViewModel book)
{
List<Author> authors = new List<Author>();
authors.Add(new Author(1, "Morgan Beaman"));
authors.Add(new Author(2, "Cleveland Lemmer"));
authors.Add(new Author(3, "Joanie Wann"));
authors.Add(new Author(4, "Emil Shupp"));
authors.Add(new Author(5, "Zenia Witts"));
book.AuthorsList = new SelectList(authors, "AuthorID", "AuthorName");
List<AuthorRole> authorRoles = new List<AuthorRole>();
authorRoles.Add(new AuthorRole(1, "Author"));
authorRoles.Add(new AuthorRole(2, "Editor"));
authorRoles.Add(new AuthorRole(3, "Translator"));
book.AuthorRolesList = new SelectList(authorRoles, "AuthorRoleID", "AuthorRoleName");
}
returns 观点:
@{
Layout = "_DetailsLayout";
}
@model Test.BookViewModel
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-sm-8 col-lg-6">
<form asp-action="SaveBook" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="BookID" />
<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="NumberOfPages" class="control-label"></label>
<input asp-for="NumberOfPages" class="form-control" />
<span asp-validation-for="NumberOfPages" class="text-danger"></span>
</div>
<label asp-for="BookAuthors" class="control-label"></label>
@await Html.PartialAsync("_AuthorDetails", Model)
<div id="AuthorDetailsWrapper"></div>
<div class="clearfix">
<a class="btn btn-link text-success btn-sm float-right" data-ajax="true"
data-ajax-url="@Url.Action("AddAuthor", new { id = Model.BookID })"
data-ajax-method="GET" data-ajax-mode="after"
data-ajax-loading-duration="400"
data-ajax-update="#AuthorDetailsWrapper" href="">ADD AUTHOR</a>
</div>
<div class="row mt-5">
<div class="col">
<a asp-action="Index" class="btn btn-outline-secondary block-on-mobile mb-2">Back</a>
</div>
<div class="col">
<button type="submit" class="btn btn-success float-right block-on-mobile">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
如果图书有多位作者,用户应单击调用操作的“添加作者”
[HttpGet]
public IActionResult AddAuthor(int? bookid)
{
BookViewModel book = new BookViewModel();
BookAuthor newAuthor = new BookAuthor();
if (bookid != null)
{
newAuthor.BookID = bookid;
}
PopulateDropdowns(book);
book.BookAuthors.Add(newAuthor);
return PartialView("_AuthorDetails", book);
}
returns 观点:
@model Test.BookViewModel
@for (var i = 0; i < Model.BookAuthors.Count; i++)
{
string guid = Guid.NewGuid().ToString();
<div class="form-row">
<input type="hidden" name="BookAuthors.Index" value="@guid" />
<input asp-for="@Model.BookAuthors[i].BookID" type="hidden" data-guid="@guid" />
<input asp-for="@Model.BookAuthors[i].BookAuthorID" type="hidden" data-guid="@guid" />
<div class="form-group col">
<label asp-for="@Model.BookAuthors[i].AuthorID"></label>
<select asp-for="@Model.BookAuthors[i].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="@Model.BookAuthors[i].AuthorID" class="text-danger" data-guid="@guid"></span>
</div>
<div class="form-group col">
<label asp-for="@Model.BookAuthors[i].AuthorRoleID"></label>
<select asp-for="@Model.BookAuthors[i].AuthorRoleID" asp-items="Model.AuthorRolesList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="@Model.BookAuthors[i].AuthorRoleID" data-guid="@guid" class="text-danger"></span>
</div>
</div>
}
在提交表单时,应将模型传递给操作:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveBook(BookViewModel book)
{
// validation and saving book to database
}
所有属性 - 标题和页数,还有完整的作者列表,每个都有 AuthorID 和 AuthorRoleID。
问题是作者的属性(为下拉列表选择的 AuthorID 和 AuthorRoleID)未被传递,即具有空值。
例如,如果我这样填写表格
Filled form, with two authors
对于列表 BookAuthors 的两个元素,预置 AuthorID 和 AuthorRoleID 为空:
Data not passed to action
我认为出现问题是因为 AuthorID(和 AuthorRoleID)的“asp-for”标签助手为 select 元素的“id”和“name”属性生成了错误的值 - 它使用 0作为索引,而不是指导。所以而不是
<select data-guid="283a0dea-9d64-432f-a03d-8c1a167b062a" class="dropdowns" id="BookAuthors_283a0dea-9d64-432f-a03d-8c1a167b062a__AuthorID" name="BookAuthors[283a0dea-9d64-432f-a03d-8c1a167b062a].AuthorID">
<option></option>
<option value="1">Morgan Beaman</option>
<option value="2">Cleveland Lemmer</option>
<option value="3">Joanie Wann</option>
<option value="4">Emil Shupp</option>
<option value="5">Zenia Witts</option>
</select>
元素看起来像这样
<select data-guid="283a0dea-9d64-432f-a03d-8c1a167b062a" class="dropdowns" id="BookAuthors_0__AuthorID" name="BookAuthors[0].AuthorID">
<option></option>
<option value="1">Morgan Beaman</option>
<option value="2">Cleveland Lemmer</option>
<option value="3">Joanie Wann</option>
<option value="4">Emil Shupp</option>
<option value="5">Zenia Witts</option>
</select>
如果我在提交前在 DevTools 中编辑此元素(我用 guid 值替换零),所有内容都会按预期提交。
让我感到困惑的是,如果 AuthorID 不是 select 元素,而是 input 元素,则相同的代码仍然有效。例如,在 _AuthorDetails.cshtml 部分视图中,如果我替换
<select asp-for="@Model.BookAuthors[i].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
和
<input asp-for="@Model.BookAuthors[i].AuthorID" data-guid="@guid" />
生成的元素是:
<input data-guid="926eb580-d52f-4132-ab43-b8fbb63e3d2c" type="number" id="BookAuthors_926eb580-d52f-4132-ab43-b8fbb63e3d2c__AuthorID" name="BookAuthors[926eb580-d52f-4132-ab43-b8fbb63e3d2c].AuthorID" value="" minlength="0">
如果我手动输入作者 ID 并提交表单,数据将通过: Filled part of form, 5 is entered for author's id Data passed to action
那么,我该如何修正这段代码,以便生成具有正确索引的 select 元素?
此外,替代解决方案和一般改进技巧也很受欢迎。可能的替代解决方案应该允许在同一页面上添加(以及稍后编辑,使用相同的视图)具有任意数量作者的书籍...
if I fill the form like this Filled form, with two authors for both elements of list BookAuthors, preoperties AuthorID and AuthorRoleID are null
那是因为你的partial view包含了一个input元素,它不是BookAuthors中的属性,而你定义为BookAuthors.Index
,模型绑定系统会误解它:
<input type="hidden" name="BookAuthors.Index" value="@guid" />
要解决此问题,只需更改如下名称,避免使用 BookAuthors.xxx
或 BookAuthors[i].xxx
:
<input type="hidden" name="data" value="@guid" />
更新:
无法将列表传递给后端的原因是每次渲染局部视图时,BookAuthors 的索引总是 0.You 需要渲染 html 如下所示:
一个简单的解决方法是设置一个 属性 来记录 BookAuthors.Change 您的代码的索引,如下所示:
型号:
public class BookViewModel
{
//more properties...
//add Index property...
public int Index { get; set; }
[DisplayName("Authors")]
public List<BookAuthor> BookAuthors { get; set; }
public BookViewModel()
{
BookAuthors = new List<BookAuthor>();
}
}
_AuthorDetails.cshtml:
点击添加作者按钮时不要使用标签helper.Because,索引将更改为1,标签助手不允许列表计数来自1.Use 姓名属性而不是标签助手。
@model BookViewModel
@{
string guid = Guid.NewGuid().ToString();
}
<div class="form-row">
<input type="hidden" name="Index" value="@guid" />
<input name="BookAuthors[@Model.Index].BookID" type="hidden" data-guid="@guid" />
<input name="BookAuthors[@Model.Index].BookAuthorID" type="hidden" data-guid="@guid" />
<div class="form-group col">
<label asp-for="BookAuthors[Model.Index].AuthorID"></label>
<select name="BookAuthors[@Model.Index].AuthorID" asp-items="Model.AuthorsList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="BookAuthors[Model.Index].AuthorID" class="text-danger" data-guid="@guid"></span>
</div>
<div class="form-group col">
<label asp-for="BookAuthors[Model.Index].AuthorRoleID"></label>
<select name="BookAuthors[@Model.Index].AuthorRoleID" asp-items="Model.AuthorRolesList" data-guid="@guid" class="dropdowns">
<option></option>
</select>
<span asp-validation-for="BookAuthors[Model.Index].AuthorRoleID" data-guid="@guid" class="text-danger"></span>
</div>
</div>
控制器:
[HttpGet]
public IActionResult AddAuthor(int? bookid)
{
var index = HttpContext.Session.GetInt32("item").Value;
BookViewModel book = new BookViewModel();
BookAuthor newAuthor = new BookAuthor();
if (bookid != null)
{
newAuthor.BookID = bookid;
}
PopulateDropdowns(book);
book.BookAuthors.Add(newAuthor);
book.Index= index+1;
HttpContext.Session.SetInt32("item", book.Index);
return PartialView("_AuthorDetails", book);
}
public IActionResult AddBook()
{
BookViewModel book = new BookViewModel();
PopulateDropdowns(book);
book.BookAuthors.Add(new BookAuthor());
book.Index = 0;
HttpContext.Session.SetInt32("item", book.Index);
return View(book);
}
确保在 Startup.cs:
中注册会话public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
//....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
//...
}
结果: