将数据注释应用于MVC中View模型的子属性?

时间:2013-06-17 17:22:04

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

在属性上放置简单的数据注释很棒,

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; }

4 个答案:

答案 0 :(得分: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,因为你已经在那个页面上,但是在其他地方使用了儿童模板,它应该显示到父母的链接)。最终我停止使用模板,并偶尔使用部分,其中有很多重用的好例子。用户界面是橡胶与道路相遇的地方,而ViewModels的结构与实体/商业模式不同。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

我个人非常喜欢使用ModelMetadataClass来管理我的ViewModel。如果您愿意继续使用AutoMapper,可以创建一个viewmodel,如下所示:

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);

答案 3 :(得分:0)

这可能是一个迟到的答案,但我在寻找同样的事情时发现了这个问题。 这就是我解决我的特殊情况的方法:

在我这之前:

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; }
    }
}

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