【问题标题】:How can I bind selected properties of a Model to a Razor Page?如何将模型的选定属性绑定到 Razor 页面?
【发布时间】:2022-01-25 06:43:22
【问题描述】:

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

public class ThisPersonPageModel : PageModel
{

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

    … OnGet() …

    … OnPost() …
}

实体是一个带注释的 EF Core 模型类(“域模型”),内置了所有约束和验证。但是这个实体上的这个特定视图只显示了一些模型属性,并且绑定器想要绑定所有他们。因此,在保存数据时,字段值会被删除。我期待能够使用 [BindProperty(Include="Field1,Field2")] 进行注释(类似于 MVC 的'Bind(Include="Field1,Field2")]')。但是 Razor Pages 显然不存在这种“字段选择”功能。可以? (或者有人知道它是否/何时到来?)

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

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

【问题讨论】:

    标签: c# razor razor-pages model-binding two-way-binding


    【解决方案1】:

    您可以将发布的表单值绑定到公共属性,但您不必这样做。您可以改为绑定到 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 MyApp.Models.Person ThisPerson { get; set; }
    

    您只是不需要绑定到它。只需确保参数和公共属性同名即可。

    【讨论】:

    • 非常感谢 Mike,您提供了我需要的解决方案。这是合适的,因为您的出色文章 telerik.com/blogs/…> 让我开始了解这一切。干杯!
    【解决方案2】:

    感谢 Mike Brind,我发现了自己的错误。

    我现在意识到,我通过一些魔术假设 Person 实体值被保存在某个地方(如 Web 窗体 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() …
    

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

    【讨论】:

      猜你喜欢
      • 2017-05-13
      • 2019-02-03
      • 1970-01-01
      • 1970-01-01
      • 2021-01-25
      • 2017-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多