ASP.NET 核心 MVC - 编辑实体列表
ASP.NET Core MVC - editing a list of entities
我正在尝试使用 ASP.NET Core 5 MVC 进行前端开发 - 但到目前为止我并没有取得太大的成功......我更像是一名后端开发人员 - 给我有一个 SQL 服务器数据库和一个 ASP.NET 核心 Web API,我很高兴 - 但这种前端魔法并不是我的强项......
好的,所以我正在尝试一些非常简单的东西——一个Razor页面来编辑一些东西的列表,为每一个东西添加一个数值,然后存储它们.
我有一个 model/DTO class 像:
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
当然,在现实中,它还有更多的属性——但这些与这里无关。什么是:给出了名称和城市,我需要向其中添加 CustomerId
(在我们的例子中,CustomerId
由 SAP 处理 - 所以我们需要在客户被选中后手动添加它们在那里创建)。
我有一个非常简单的 Razor 页面,它基本上显示了客户列表,允许我在最后一列中输入 CustomerId
,然后我在同一个 Razor 上有一个 POST
方法当用户点击 Update
(提交)按钮时点击的页面。
这是我的观点:
@page
@model IndexModel
<h3 class="display-4">Customers</h3>
@if (!string.IsNullOrWhiteSpace(@Model.Message))
{
<div class="container">
<span class="@Model.MessageClass mt-4 mb-5">@Model.Message</span>
</div>
}
<div class="container">
<form method="post">
<table class="table table-striped table-bordered table-hover table-sm">
<thead class="thead-dark">
<tr>
<th scope="col-md-2">Name</th>
<th scope="col-md-2">City</th>
<th scope="col-md-1">CustomerId</th>
</tr>
</thead>
<tbody>
@for (int index = 0; index < Model.Customers.Count; index++)
{
<tr>
<td>@Model.Customers[index].Name</td>
<td>@Model.Customers[index].City</td>
<td>
<input type="number" asp-for="Customers[index].CustomerId" id="Customers[@index]" />
</td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Update" class="btn btn-primary" />
</form>
</div>
这是我在这个 Razor 页面上的“code-behind”:
public class IndexModel : PageModel
{
public string Message { get; set; }
public string MessageClass { get; set; }
public List<Customer> Customers { get; set; }
public async Task OnGetAsync()
{
Customers = CustomerProvider.GetCustomers();
}
public async Task OnPostAsync()
{
Customers = CustomerProvider.GetCustomers();
if (await TryUpdateModelAsync(Customers))
{
Message = "Customers successfully updated";
MessageClass = "alert-success";
}
else
{
Message = "Customer update did not work";
MessageClass = "alert-danger";
}
}
}
这里没什么特别的 - 我基本上是从某个地方(实际上是一个数据库)获取客户列表,我显示该列表,我可以将客户 ID 值输入到网格中,我期待得到在我的 OnPostAsync
方法中更新了 Customers
。
现在代码运行良好,显示客户列表,我可以在 CustomerId
列中输入值,然后单击“更新”。 OnPostAsync
方法被调用,我再次获取客户,并且我期待 await TryUpdateModelAsync
使用我输入的 CustomerId
值更新我的客户。
但它不会那样做 - 在我的 GetCustomers
电话之后,我有四个测试客户 - 正如预期的那样 - 但在 TryUpdateModelAsync
之后,该列表是空的......对 TryUpdateModelAsync
的调用有效 - 它 returns true
- 但客户列表未使用屏幕上输入的信息更新 - 恰恰相反,列表已删除...
我也试过用
public async Task OnPostAsync([FromForm] List<Customer> updatedCustomers)
希望 MVC 数据绑定 return 返回更新的客户列表 - 但是这个 updatedCustomers
是 null
并且不会发回输入的数据...
但是当我查看 HttpContext.Request.Form
- 我 确实看到了 输入的值:
但不知何故,这些处理不当且未应用于 Customers
列表...
有什么想法吗?我一定是在某处遗漏了一些非常愚蠢的东西 - 但我就是找不到它......
啊,如果我理解这个案例,那你就漏掉了一小部分。
所以你想向你的 POST 方法发送一个 list/array 类型的参数,但是你的 POST 方法不知道作为参数传入的对象。所以你应该在你的输入上定义一个 name
属性。
<input type="number" name="customerIds" asp-for="Customers[index].CustomerId" id="Customers[@index]" />
并且您的 POST 方法应符合 int[]
类型的参数。
public async Task OnPostAsync(int[] customerIds)
{
//Now you have edited customerIds as an integer array type and you can manuplate it.
}
您可以通过两种方式绑定值:
1。使用 [BindProperty]
为您的模型添加一个 属性 并用 [BindProperty]
注释它。当您使用 asp-for="@Model.MyList[i].AProp"
构建输入时,它将在提交时绑定到表单值。
注意:您仍然需要在 HTML 中使用隐藏输入 (<input type="hidden" />
) 呈现只读属性,以便这些值在表单已提交,否则您将获得 sentinel/null 个值。
假设您有一个 Razor 模板如下:
<form method="POST">
@Html.AntiForgeryToken()
<table>
<thead>
<tr>
<th>Customer Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@for (var i = 0; i < Model.Customers.Count; i++)
{
<tr>
<td>
<input type="text"
readonly
value="@Model.Customers[i].CustomerId"
asp-for="@Model.Customers[i].CustomerId"/> <!-- asp-for attributes are indexed for each item -->
</td>
<td>
<input type="text"
value="@Model.Customers[i].Name"
asp-for="@Model.Customers[i].Name"/>
</td>
</tr>
}
</tbody>
</table>
<button type="submit">Save</button>
</form>
你需要一个有边界的模型 属性:
public record Customer(string CustomerId, string Name);
public class HomeModel : PageModel
{
[BindProperty]
public List<Customer> Customers { get; set; }
// ...
}
呈现时,您应该有带索引 name
属性的输入:
<input type="text" value="Bob" id="Customers_1__Name" name="Customers[1].Name"/>
当您提交表单时,Model.Customers
将填充值。
2。使用 [FromForm]
属性
您还可以接受与输入名称同名的参数。这意味着如果输入名称类似于 Customers[1].Name
,则参数名称必须是 customers
(不区分大小写)(而不是像您使用的 updatedCustomers
)。或者您可以使用 [FromForm(Name = "customers")] updatedCustomers
.
指定不同的名称
// this works
public ActionResult OnPost([FromForm]List<Customer> customers)
{
return Page();
}
// this also works
public ActionResult OnPost([FromForm(Name = "customers")]List<Customer> updatedCustomers)
{
return Page();
}
如果模型有一个绑定 属性 ([BindProperty]
),除了参数之外,它还会填充表单值。
更多信息
我正在尝试使用 ASP.NET Core 5 MVC 进行前端开发 - 但到目前为止我并没有取得太大的成功......我更像是一名后端开发人员 - 给我有一个 SQL 服务器数据库和一个 ASP.NET 核心 Web API,我很高兴 - 但这种前端魔法并不是我的强项......
好的,所以我正在尝试一些非常简单的东西——一个Razor页面来编辑一些东西的列表,为每一个东西添加一个数值,然后存储它们.
我有一个 model/DTO class 像:
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
当然,在现实中,它还有更多的属性——但这些与这里无关。什么是:给出了名称和城市,我需要向其中添加 CustomerId
(在我们的例子中,CustomerId
由 SAP 处理 - 所以我们需要在客户被选中后手动添加它们在那里创建)。
我有一个非常简单的 Razor 页面,它基本上显示了客户列表,允许我在最后一列中输入 CustomerId
,然后我在同一个 Razor 上有一个 POST
方法当用户点击 Update
(提交)按钮时点击的页面。
这是我的观点:
@page
@model IndexModel
<h3 class="display-4">Customers</h3>
@if (!string.IsNullOrWhiteSpace(@Model.Message))
{
<div class="container">
<span class="@Model.MessageClass mt-4 mb-5">@Model.Message</span>
</div>
}
<div class="container">
<form method="post">
<table class="table table-striped table-bordered table-hover table-sm">
<thead class="thead-dark">
<tr>
<th scope="col-md-2">Name</th>
<th scope="col-md-2">City</th>
<th scope="col-md-1">CustomerId</th>
</tr>
</thead>
<tbody>
@for (int index = 0; index < Model.Customers.Count; index++)
{
<tr>
<td>@Model.Customers[index].Name</td>
<td>@Model.Customers[index].City</td>
<td>
<input type="number" asp-for="Customers[index].CustomerId" id="Customers[@index]" />
</td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Update" class="btn btn-primary" />
</form>
</div>
这是我在这个 Razor 页面上的“code-behind”:
public class IndexModel : PageModel
{
public string Message { get; set; }
public string MessageClass { get; set; }
public List<Customer> Customers { get; set; }
public async Task OnGetAsync()
{
Customers = CustomerProvider.GetCustomers();
}
public async Task OnPostAsync()
{
Customers = CustomerProvider.GetCustomers();
if (await TryUpdateModelAsync(Customers))
{
Message = "Customers successfully updated";
MessageClass = "alert-success";
}
else
{
Message = "Customer update did not work";
MessageClass = "alert-danger";
}
}
}
这里没什么特别的 - 我基本上是从某个地方(实际上是一个数据库)获取客户列表,我显示该列表,我可以将客户 ID 值输入到网格中,我期待得到在我的 OnPostAsync
方法中更新了 Customers
。
现在代码运行良好,显示客户列表,我可以在 CustomerId
列中输入值,然后单击“更新”。 OnPostAsync
方法被调用,我再次获取客户,并且我期待 await TryUpdateModelAsync
使用我输入的 CustomerId
值更新我的客户。
但它不会那样做 - 在我的 GetCustomers
电话之后,我有四个测试客户 - 正如预期的那样 - 但在 TryUpdateModelAsync
之后,该列表是空的......对 TryUpdateModelAsync
的调用有效 - 它 returns true
- 但客户列表未使用屏幕上输入的信息更新 - 恰恰相反,列表已删除...
我也试过用
public async Task OnPostAsync([FromForm] List<Customer> updatedCustomers)
希望 MVC 数据绑定 return 返回更新的客户列表 - 但是这个 updatedCustomers
是 null
并且不会发回输入的数据...
但是当我查看 HttpContext.Request.Form
- 我 确实看到了 输入的值:
但不知何故,这些处理不当且未应用于 Customers
列表...
有什么想法吗?我一定是在某处遗漏了一些非常愚蠢的东西 - 但我就是找不到它......
啊,如果我理解这个案例,那你就漏掉了一小部分。
所以你想向你的 POST 方法发送一个 list/array 类型的参数,但是你的 POST 方法不知道作为参数传入的对象。所以你应该在你的输入上定义一个 name
属性。
<input type="number" name="customerIds" asp-for="Customers[index].CustomerId" id="Customers[@index]" />
并且您的 POST 方法应符合 int[]
类型的参数。
public async Task OnPostAsync(int[] customerIds)
{
//Now you have edited customerIds as an integer array type and you can manuplate it.
}
您可以通过两种方式绑定值:
1。使用 [BindProperty]
为您的模型添加一个 属性 并用 [BindProperty]
注释它。当您使用 asp-for="@Model.MyList[i].AProp"
构建输入时,它将在提交时绑定到表单值。
注意:您仍然需要在 HTML 中使用隐藏输入 (<input type="hidden" />
) 呈现只读属性,以便这些值在表单已提交,否则您将获得 sentinel/null 个值。
假设您有一个 Razor 模板如下:
<form method="POST">
@Html.AntiForgeryToken()
<table>
<thead>
<tr>
<th>Customer Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@for (var i = 0; i < Model.Customers.Count; i++)
{
<tr>
<td>
<input type="text"
readonly
value="@Model.Customers[i].CustomerId"
asp-for="@Model.Customers[i].CustomerId"/> <!-- asp-for attributes are indexed for each item -->
</td>
<td>
<input type="text"
value="@Model.Customers[i].Name"
asp-for="@Model.Customers[i].Name"/>
</td>
</tr>
}
</tbody>
</table>
<button type="submit">Save</button>
</form>
你需要一个有边界的模型 属性:
public record Customer(string CustomerId, string Name);
public class HomeModel : PageModel
{
[BindProperty]
public List<Customer> Customers { get; set; }
// ...
}
呈现时,您应该有带索引 name
属性的输入:
<input type="text" value="Bob" id="Customers_1__Name" name="Customers[1].Name"/>
当您提交表单时,Model.Customers
将填充值。
2。使用 [FromForm]
属性
您还可以接受与输入名称同名的参数。这意味着如果输入名称类似于 Customers[1].Name
,则参数名称必须是 customers
(不区分大小写)(而不是像您使用的 updatedCustomers
)。或者您可以使用 [FromForm(Name = "customers")] updatedCustomers
.
// this works
public ActionResult OnPost([FromForm]List<Customer> customers)
{
return Page();
}
// this also works
public ActionResult OnPost([FromForm(Name = "customers")]List<Customer> updatedCustomers)
{
return Page();
}
如果模型有一个绑定 属性 ([BindProperty]
),除了参数之外,它还会填充表单值。