将对象发送到控制器,包括外键

问题描述 投票:0回答:1

我正在尝试创建一个名为

Facture
的对象,它有一个外键
Client
:

public class Facture
{
    public int Id { get; set; }

    [ValidateNever]
    [DataType(DataType.Date)]
    public DateTime? DateCreation { get; set; }

    [Required]
    [DataType(DataType.Date)]
    [Display(Name = "Date de facturation")]
    public DateTime DateFacturation { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Date d'échéance")]
    public DateTime? DateEcheance { get; set; }

    [ValidateNever]
    public required Client Client { get; set; }
}

public class Client
{
    public int Id { get; set; }

    [Required]
    public required string Name { get; set; }

    ICollection<Facture>? Factures { get; set; }
}

事实是我不知道如何获取从视图发送的

Client
的值。我已经在我的
FacturesController
:

中填充了一个下拉列表
    public IActionResult Create()
    {
        ViewBag.Client = new SelectList(_context.Client, "Id", "Name");
        return View();
    }

Facture
视图中:

<form asp-action="Create">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <input asp-for="DateCreation" class="form-control" />
        <span asp-validation-for="DateCreation" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="DateFacturation" class="control-label"></label>
        <input asp-for="DateFacturation" class="form-control" />
        <span asp-validation-for="DateFacturation" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="DateEcheance" class="control-label"></label>
        <input asp-for="DateEcheance" class="form-control" />
        <span asp-validation-for="DateEcheance" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Client" class="control-label"></label>
        <select asp-for="Client" class="form-control" asp-items="ViewBag.Client"></select>
    </div>
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
</form>

而且我不知道如何在第二个

Create
方法中获取client的值:

public async Task<IActionResult> Create([Bind("Id,DateFacturation,DateEcheance,Client")] Facture facture)
{
    if (ModelState.IsValid)
    {
        facture.DateCreation = DateTime.Now;
        _context.Add(facture);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    return View(facture);
}
c# asp.net-core .net-core asp.net-core-mvc
1个回答
0
投票

简短的回答是“不要将实体发送到视图,项目到视图模型”。数据不存在是因为您可能一开始就没有提供数据。意思是,在将 Facture 发送到视图时忘记了

.Include(f => f.Client)
,和/或没有视图 JS 的输入来重新构建要发回的客户端数据。

详细答案:控制器提供给 MVC 视图的对象不会发送到浏览器。它们被发送到视图引擎。视图引擎构建 html + JS 视图发送到浏览器。 “视图”没有模型是什么/曾经是什么的概念。它所拥有的只是视图引擎组成的

<form>
元素中的输入控件。如果您使用浏览器中的调试工具并检查您的页面,您将发现没有“模型”,只有 HTML 元素。当您的页面回发或获取要在 Ajax 调用等中使用的 FormData 时,输入元素的 Id 和值将组成一个简单的 JSON 对象并发送到控制器。然后,MVC 将其与控制器操作中的参数结合起来。

因此,为了让您的 MVC 控制器操作接收“Facture”,它将构造一个新的 Facture 实例,并根据传入的 JSON 中具有相同名称的值填充属性。任何不匹配的内容都会被忽略。如果您想获取相关数据,那么您需要在视图上拥有已映射的输入控件,并根据相关数据命名。这些将被命名为遵循 MVC 将遵循的约定来重新饱和相关实体的实例。通常,您没有/不需要这些字段的可见输入,因此实践是使用隐藏输入。 IE。

@Html.HiddenFor(model => model.ClientId)
如果您希望在返回时填充整个客户端实体,则需要为每个客户端属性提供输入。可能,但过度且不必要。最需要注意的是,您没有获得发送到视图引擎的相同对象实例,甚至没有获得控制器操作中 DbContext 跟踪的对象实例。

ViewModel 更好用,因为它们打破了这个错误的假设。您知道您将获得一个简单的“视图”模型,并且需要将其转换为新的模型,或者获取现有的“实体”以将模型转回其中。

当涉及到为新 Facture 选择“客户端”并填充客户端/w ClientId 和名称下拉列表的关联时,该 ClientId 将映射到模型中的属性。例如,如果这是一个新的 Facture 实体,并且您希望设置 Facture 的客户端,则从 DbContext 中获取它:

public async Task<IActionResult> Create([Bind("Id,DateFacturation,DateEcheance,Client")] Facture facture)
{
    if (ModelState.IsValid)
    {
        var client = _context.Clients.Single(c => c.ClientId == facture.ClientId);
        facture.DateCreation = DateTime.Now;
        facture.Client = client;
        _context.Add(facture);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    return View(facture);

}

即使您为 Facture.Client 中的每个属性添加了隐藏输入,并确保在选择 Client 时填充它们,并在将 Facture“添加”到 DbContext 时在回调到控制器时传回填充facture.Client ,客户端引用不是跟踪引用,因此 EF 也会将其视为插入,从而导致重复数据异常,或插入具有新 PK 的新重复行。

我建议养成这样的习惯:将视图模型投影并传递给视图(在 GET 操作上使用

Select()
),然后在 POST/PUT/PATCH 上组合和查找实体。它可以避免与引用混淆,并避免可能会暴露出比您预期更多的域详细信息的问题,或者更糟糕的是,可能会出现不应更新的数据被覆盖的问题。 (如果您正在进行
_context.Update(facture);
之类的编辑操作)

© www.soinside.com 2019 - 2024. All rights reserved.