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 返回更新的客户列表 - 但是这个 updatedCustomersnull 并且不会发回输入的数据...

但是当我查看 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]),除了参数之外,它还会填充表单值。

更多信息