如何将模型的选定属性绑定到 Razor 页面?

How can I bind selected properties of a Model to a Razor Page?

我刚搬到 Razor Pages,但碰壁了。这是一个页面模型:

public class ThisPersonPageModel : PageModel
{

    [BindProperty]
    public MyApp.Models.Person ThisPerson { get; set; }

    … OnGet() …

    … OnPost() …
}

该实体是带注释的 EF Core 模型 class(“域模型”),内置所有约束和验证。但此实体的特定视图仅显示部分模型属性,并且活页夹想要绑定所有这些。因此,保存数据时字段值会被删除。我期望能够使用 [BindProperty(Include=”Field1,Field2”)] 进行注释(类似于 MVC 的“Bind(Include=”Field1,Field2”)]”)。但是 Razor Pages 显然不存在这种“字段选择”功能。可以? (或者有人知道 if/when 它要来了吗?)

对我来说,这是一个严重的限制,甚至是使用 Razor Pages 的障碍。我想获得双向模型绑定的美丽 DRY 好处,主要是客户端验证(来自模型注释)并且在检索值时无需编写(重复)绑定代码。然而,我一直拥有具有广泛功能(许多属性)的域模型(数据库表),但是,对于每个视图,根据特定用户(角色)的情况、需求和权限,只显​​示特定属性。 (我认为这就是视图的本质:针对特定用户和情况的数据的特定 window。)例如,一个人可以更新他们的一些帐户属性(例如姓名),但只能管理员可以更新其他属性(例如访问级别)。或者,同一个人在整个生命周期中处理复杂实体的不同部分:尽早填充早期属性,稍后填充后期属性。 (想想一个向导 UI。)您不希望在保存较晚的属性时删除较早的属性。对我来说,这种情况无处不在,多年来一直用于许多数据库,由我和其他人设计。

我已经研究了几个选项,但没有任何结果。我在很多地方看到过这个答案:“只需创建一个只包含此视图所需属性的 PersonViewModel”。但这当然不是 DRY(完美的领域模型已经存在),它需要映射 to/from 领域模型,以及领域模型上所有漂亮、复杂的约束和验证呢?那些也必须在本地页面模型上重新创建吗?那对我来说几乎就结束了。我见过使用 AutoMapper,但我不确定这是否会映射 to/from 具有较少属性的模型,以及在从域模型映射到页面模型时它是否保留约束和验证。这只是增加了 WET 的复杂性,“[BindProperty(Include=”Field1,Field2”)]”可以轻松解决。对我来说,这似乎是 Razor Pages 中双向模型活页夹的严重限制。我错过了什么?完成此任务的最佳方法是什么?欢迎您提出替代方案 and/or 纠正我的误解。

您可以将发布的表单值绑定到 public 属性,但您不必这样做。您可以改为绑定到 OnPost 方法上的参数。您可以在参数上使用 [Bind(include:"Field1, Field2")] 来减轻过度发布攻击:

public IActionResult OnPost([Bind(include:"Field1, Field2")] MyApp.Models.Person thisPerson)
{
    if (ModelState.IsValid)
    {
        //process form submission
        return RedirectToPage("/success");
    }
    return Page();
}

您仍然有 public 属性 激活强类型输入标签助手:

public MyApp.Models.Person ThisPerson { get; set; }

您只是不需要绑定到它。只需确保参数和 public 属性 共享相同的名称。

感谢 Mike Brind,我看到了我的方法中的错误。

我现在意识到我是通过某种魔法假设 Person 实体值被保存在某个地方(比如 Web Forms ViewState 大声笑)并且返回到 OnPost 的对象将包含原始属性和由更新的特定属性风景。当然,它们没有存储在某个地方,您的回答为我澄清了这一点。我是新手,我一直在使用 Razor Page CRUD 脚手架生成的股票代码,如下所示:

    public async Task<IActionResult> OnPostAsync(Bind(include:“FirstName,LastName”))
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Person).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        Catch ...

该代码只是将返回的实体设置为已修改状态并保存整个实体。当模型字段与视图字段完全匹配时这很好,但当视图仅管理特定字段时就不行了。所以,我切换到另一种常见形式:

    private string FieldList = “FirstName, LastName”;
    
    public async Task<IActionResult> OnPostAsync(Bind(include: FieldList))
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        Person retrievedPerson = _context.Persons.FirstOrDefault(_ => _.id == id)

        //Copy over the fields changed by the view:
        retrievedPerson.FirstName = Person.FirstName;
        retrievedPerson.LastName = Person.LastName;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch ...

所以这个解决方案对我有用,保留了双向活页夹的大部分优点,尽管有一些代码可以分配更新的属性。然而,一个简单的反射 属性 赋值方法可以减少:

//Copy over the fields changed by the view (better version):
TransferUpdatedValues<Person>(retrievedPerson, Person, FieldList)  // loop through the fields with … .SetValue(….GetValue() …

这就是我现在使用的。非常感谢您的回答,迈克。我欢迎对这种方法提出任何评论、批评和建议。