【问题标题】:Applying Data Annotations to sub properties of the View Model in MVC?将数据注释应用于 MVC 中视图模型的子属性?
【发布时间】:2013-06-17 17:22:04
【问题描述】:

在属性上添加简单的数据注释很棒,

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

但是假设我有这样的事情:

public class SuperPower
{
   public class Name { get; set; }
}

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

   public SuperPower PrimarySuperPower { get; set; }

   public SuperPower SecondarySuperPower { get; set; }

如何在 PrimarySuperPower.Name 上应用Required 属性,同时为 SecondarySuperPower.Name 保留可选属性?最好是 1. 与客户端验证相关的东西和 2. 没有任何特殊处理,例如检查 Action/Custom 验证器中 PrimarySuperPower.Name 的值,如果它为空,则添加 ModelState 错误。如果有类似的东西那就太好了:

   [Required(p => p.Name)]
   public SuperPower PrimarySuperPower { get; set; }

   public SuperPower SecondarySuperPower { get; set; }

【问题讨论】:

  • 属性如何知道它附加到什么?属性只是元数据。

标签: c# asp.net-mvc validation data-annotations


【解决方案1】:

一般不支持:ASP.NET MVC3 Validation of nested view model object fields

但是您可以实现自定义模型验证,但在客户端和服务器端都这样做会变得相当复杂。

如果您有自己的 SuperPower 对象模板,它可以查找您自己制作的属性:

   [RequiredSubProperty("Name")]
   public SuperPower PrimarySuperPower { get; set; }

在模板中,将不显眼的验证属性添加到 TextBoxFor 的 htmlAttributes 参数或您使用的任何输入助手中。

如果您不使用模板,我会放弃所有这些,只在显示第一个名称时将不显眼的验证属性传递给 htmlAttributes 参数,而不是第二个。

另一种选择是将 UnicornViewModel 扁平化

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

   [Required]
   public string PrimarySuperPowerName { get; set; }

   public string SecondarySuperPowerName { get; set; }

这完全取决于您可以从更复杂的方法中获得多少重用。当我尝试大量使用模板时,我发现在不同的上下文中,关于模板的某些事情没有意义,因此我需要对对象模板进行大量变体(当子模板显示在父页面上时,它对于孩子来说,拥有一个链接到父母详细信息的 URL 是没有意义的,因为您已经在该页面上,但是在使用子模板的其他任何地方,它都应该显示该链接到父母)。最终我停止使用模板,偶尔在有很多重用情况的情况下使用部分模板。 UI 是橡胶与道路相遇的地方,ViewModel 的结构不会像您的实体/业务模型那样好。

【讨论】:

    【解决方案2】:

    这可能是一个迟到的答案,但我在搜索相同内容时发现了这个问题。 这就是我解决我的特殊情况的方法:

    在我有这个之前:

    public class ProductVm
    {
        //+ some other properties        
    
        public Category Category {get; set;}
        public Category ParentCategory {get; set;}
    }
    

    为此我想拥有类似的东西:

    public class ProductVm
    {
        //some other properties        
    
        [DisplayName("Product Category", e => e.Description)]
        public Category Category {get; set;}
        [DisplayName("Parent Category", e => e.Description)]
        public Category ParentCategory {get; set;}
    }
    

    我无法在模型本身中输入它,因为它们是同一个对象类。

    我是这样解决的(因为在这种情况下我只需要读取描述值而不是写入它):

    public class ProductVm
    {
        //some other properties        
    
        public Category Category {get; set;}
        public Category ParentCategory {get; set;}
    
        [DisplayName("Product Category")]
        public string Category => Category.Description;
    
        [DisplayName("Main Category")]
        public string ParentCategory => ParentCategory.Description;
    }
    

    您可以稍微重写它以保留剩余的私有支持字段并删除 Category 对象的属性封装,但在我的情况下,我仍然需要将它们公开以用于其他用途。

    关于上述问题,我会做以下事情:

    public class UnicornViewModel
    {
        [Required]
        public string Name { get; set; }
    
        public SuperPower PrimarySuperPower { get; set; }
    
        public SuperPower SecondarySuperPower { get; set; }
    
        [Required]
        public string PrimarySuperPowerName 
        {
            get { return PrimarySuperPower.Name; }
            set { PrimarySuperPower.Name = value; }
        }
    
        public string SecondarySuperPowerName 
        {
            get { return SecondarySuperPower.Name; }
            set { SecondarySuperPower.Name = value; }
        }
    }
    

    然后我将我的视图绑定到字符串属性并排除 SuperPower 属性。

    【讨论】:

      【解决方案3】:

      您无法使用标准数据属性执行此操作。您提到的必需语法在自定义实现中也不可能,因为没有对您尝试使用 lambda 的对象的引用。

      您最好使用第三方验证库,例如 FluentValidation。它为您的验证要求提供了相当大的灵活性。

      【讨论】:

        【解决方案4】:

        我个人非常喜欢使用 ModelMetadataClass 来管理我的 ViewModel。如果您愿意更进一步并使用AutoMapper,您可以按如下方式创建视图模型:

        public class SuperPower
        {
            public string Name { get; set; }
        }
        
        [MetadataType(typeof(UnicornViewModel.UnicornViewModelMetaData))]
        public class UnicornViewModel
        {
            public string Name { get; set; }
        
            public RequiredSuperPowerViewModel PrimarySuperPower { get; set; }
        
            public SuperPower SecondarySuperPower { get; set; }
        
            public class UnicornViewModelMetaData
            {
                [Required]
                public string Name { get; set; }
        
            }
        }
        
        [MetadataType(typeof(UnicornViewModel.UnicornViewModelMetaData))]
        public class RequiredSuperPowerViewModel : SuperPower
        {
            public class RequiredSuperPowerModelMetaData
            {
                [Required]
                public string Name { get; set; }
        
            }
        }
        

        这将允许您选择给定模型类所需的字段,而不会影响您的模型。

        如果您使用 AutoMapper,您可以按如下方式重新水化原始 SuperPower:

        SuperPower reqSuperPower = AutoMapper.Mapper.Map<RequiredSuperPowerViewModel, SuperPower>(Data.PrimarySuperPower);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-22
          • 2013-11-07
          • 2011-05-26
          • 2012-10-26
          相关资源
          最近更新 更多