【问题标题】:How do I stop the [Required] annotation from being inherited on overridden fields?如何阻止 [Required] 注释在被覆盖的字段上被继承?
【发布时间】:2013-04-11 19:43:26
【问题描述】:

我有一个电话号码通知模型(用户通过与他们的帐户相关联的电话号码收到紧急情况通知,并且他们可以设置这些电话号码的调用顺序)。大多数情况下,模型中的电话号码部分是必需的,但在创建新用户期间有一种特殊情况,我们不想强制使用它。

我制作了一个非常简单的子对象 UserCreationPhoneNotificationModel,它继承自上述 UserPhoneNotificationModel。还有一些其他小的更改,但这里的相关更改覆盖了 PhoneNumber 字段,因此不再需要它。

在父模型中是

[Required]
public virtual string PhoneNumber { get; set; }

在子模型中它只是

public override string PhoneNumber { get; set; }

我认为这可以解决问题,但显然不是。我认为问题在于RequiredAttribute 会有 Inherited = true ,但事实并非如此,所以我不完全确定为什么它会被继承到子类中。

我做了仔细检查以确保,从父类的字段中删除Required也使得子类中的字段不需要,所以它肯定是某种继承的东西。

【问题讨论】:

  • RequiredAttribute 通过 ValidationAttribute 继承自 Attribute。抽象类 Attribute 具有 Inherited = true。您最好在场景中使用自定义属性并将 Inherited 标志设置为 false。
  • 啊,我刚刚意识到我没有进一步向上看。创建一个新属性只是为了关闭 Inherited 似乎有点愚蠢,但看起来这是唯一的方法。
  • 您是否尝试过设置电话号码以通过赋予新关键字来隐藏基本实现?
  • 在 .net mvc 中更轻松、更清洁的开发的一般建议:优先组合而不是继承——使用视图模型——将注释放在 vm 上,靠近需要它们的 ui。

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


【解决方案1】:

这可以通过用同名的新属性隐藏现有属性来完成。 所以,如果父类中的属性是这样的:

[Required]
public virtual string PhoneNumber { get; set; }

您可以像这样在子类中定义新属性:

public new string PhoneNumber { get; set; }

这将在子类中隐藏 PhoneNumber 属性及其属性。现在您可以使用它的任何其他属性。例如,您可以在子类的属性中添加[PhoneNumber] 属性。

【讨论】:

  • 这个实现给我抛出了The property 'PhoneNumber' on the content type 'UserCreationPhoneNotificationModel' is autogenerated but not virtual declared 异常。我必须在子类中定义属性如下:public new virtual string PhoneNumber { get; set; }
【解决方案2】:

当您正在建模的关系不适合时,使用继承来共享行为可能会出现问题。您通常不会从使用继承在 ViewModel 之间共享行为中获得任何好处,而您可能(并且,在您的情况下,确实)会遇到问题。

最好的办法是为每个用例使用不同的模型类。如果您确实需要在 ViewModel 之间共享行为,您可以通过组合来实现。

【讨论】:

    【解决方案3】:

    下面的例子可能对你有帮助。

    public class SigninModel
    {
        [Required]
        [EmailAddress]
        public virtual string email { get; set; }
        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        public string password { get; set; }
    }
    

    这是登录模型,我已经继承到另一个模型,但不需要电子邮件,它的代码如下:

    public class ExternalLoginModel : SigninModel
    {
        public override string email { get; set; }
        public string LoginProvider { get; set; }
        public string ProviderKey { get; set; }
    }
    

    我已经覆盖了另一个模型中的电子邮件属性

    【讨论】:

      【解决方案4】:

      如果使用 C# 4.0,

      如何编写“新”构造函数。

      public new string PhoneNumber { get; set; }
      

      我不确定它是否有效。

      【讨论】:

      • 也许您可以在发布为答案之前对此进行测试。
      【解决方案5】:

      不继承属性是没有意义的

      在覆盖时删除[Required] 属性本质上违反了LSP

      如果NotRequiredDerivedRequiredBase 的子类型,则RequiredBase 类型的对象可以替换为NotRequiredDerived 类型的对象(即RequiredBase 类型的对象可以替换为任何对象子类型NotRequiredDerived),而不改变程序的任何所需属性。

      简而言之:

      var requiredPhoneNumber = new RequiredBase() { PhoneNumber = "123456789" };
      
      HandleRequiredBaseObject(requiredPhoneNumber); //Works
      
      var optionalPhoneNumber = new NotRequiredDerived() { PhoneNumber = null };
      
      HandleRequiredBaseObject(optionalPhoneNumber); //Fails
      

      HandleRequiredBaseObject(RequiredBase obj) 固有地假定PhoneNumber 是必需的(根据RequiredBase 类的定义);并且它不期望收到缺少此约束的派生对象!这将导致运行时异常。

      不违反 LSP 的唯一方法是确保在派生类上不违反 [Required] 约束;这意味着首先尝试删除 [Required] 注释是没有意义的。


      我可以想到一种理论上的 案例,其中不继承属性是有意义的:如果属性扩展 有效选项的范围,而不是限制它。

      假设我们创建了一个 PositiveNumber 类和一个将其设置为也允许负数的属性:

      public class Base 
      {
          [NegativesAllowed]
          public PositiveNumber Number { get; set; }
      }
      
      public class Derived : Base
      {
          public PositiveNumber Number { get; set; }
      }
      

      如果Base 允许所有数字,而Derived 只允许正数,那么它本质上并不违反 LSP。

      但是,这似乎是一个非常强迫的情况。使用 limited 类型然后被属性扩展并不是你真正会遇到的事情。


      对于您的情况,您根本不应该从另一个视图模型继承一个视图模型。

      需要不同的属性这一事实是您不应该继承这些类的主要原因!它们应该以不同的方式工作,因此不应假装其中一个是另一个的派生。

      【讨论】:

        猜你喜欢
        • 2016-08-29
        • 1970-01-01
        • 2021-07-11
        • 1970-01-01
        • 1970-01-01
        • 2016-08-22
        • 2021-07-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多