Is it possible to override the required attribute on a property in a model?
Asked Answered
S

5

26

I'm curious to find out if it is possible to override the [Required] attribute that has been set on a model. I'm sure there most be a simple solution to this problem, any takers?

Suter answered 18/1, 2012 at 0:55 Comment(1)
Define "override". Do you mean creating a subclass of your model where that property is not required, or do you mean making a specific controller action not care if a required field isn't provided? Are you worried about client-side validation or just server side?Youthful
F
26

Depends on what precisely you are doing. If you are working with a subclass, using the model with the Required attribute as the base, you can do this:

Redefine the property with the new keyword, rather than override it.

public class BaseModel
{
    [Required]
    public string RequiredProperty { get; set; }
}


public class DerivativeModel : BaseModel
{
    new public string RequiredProperty { get; set; }

}

If you simply want to bind or validate a model, but skip the Required property in your controller, you can do something like:

public ActionResult SomeAction()
{
     var model = new BaseModel();

     if (TryUpdateModel(model, null, null, new[] { "RequiredProperty" })) // fourth parameter is an array of properties (by name) that are excluded
     {
          // updated and validated correctly!
          return View(model);
     }
     // failed validation
     return View(model);
}
Flinders answered 18/1, 2012 at 0:58 Comment(4)
You may want to set and get from base, if others depend on the parent class field: get { return base.RequiredProperty; } set{ value = base.RequiredProperty;}Epigone
Odd but that didn't work for me. I have an attribute [Range(0,999999)] and the child class has [Range(0,2000)] but the validation picks up the parent instead of childYockey
this does not work public class BaseModel { [Required] public string RequiredProperty { get; set; } } public class DerivativeModel : BaseModel { new public string RequiredProperty { get; set; } }Popularize
I tried this and the [Required] attribute is applied to the inherited property, too. I believe the point of the original question was how to override that attribute in inherited objects.Nabors
D
20

@HackedByChinese method is fine, but it contains a problem

public class BaseModel
{
    [Required]
    public string RequiredProperty { get; set; }
}

public class DerivativeModel : BaseModel
{
    new public string RequiredProperty { get; set; }
}

This code give you a validation error in ModelState EVEN if you use DerivativeModel on the form, override doesn't work either, so you cannot delete Required attribute by overriding or renewin it, so I came to a some sort of a workaround

public class BaseModel
{
    public virtual string RequiredProperty { get; set; }
}

public class DerivativeModel : BaseModel
{
    [Required]
    public override string RequiredProperty { get; set; }
}

public class DerivativeModel2 : BaseModel
{
    [Range(1, 10)]
    public override string RequiredProperty { get; set; }
}

I have a base model with no validation attributes and derived classes

Discreditable answered 16/7, 2015 at 7:54 Comment(2)
thank you that writed about ModelState validation error, i thought that i'm doing something wrongWinch
Thank you! This was perfect for my situation where I needed the class to have validation in one view model, and readonly data in another view model.Essence
F
7

You can use a custom validation attribute (it might be derived from RequiredAttribute):

 public class RequiredExAttribute : RequiredAttribute
    {
        public bool UseRequiredAttribute { get; protected set; }
        public RequiredExAttribute(bool IsRequired)
        {
            UseRequiredAttribute = IsRequired;
        }
        public override bool IsValid(object value)
        {
            if (UseRequiredAttribute)
                return base.IsValid(value);
            else
            {
                return true;
            }
        }

        public override bool RequiresValidationContext
        {
            get
            {
                return UseRequiredAttribute;
            }
        }
    }

    public class RequiredExAttributeAdapter : RequiredAttributeAdapter
    {
        public RequiredExAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredExAttribute attribute)
            : base(metadata, context, attribute) { }

        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            if (((RequiredExAttribute)Attribute).UseRequiredAttribute)// required -> return normal required rules
                return base.GetClientValidationRules();
            else// not required -> return empty rules list
                return new List<ModelClientValidationRule>();
        }
    }

and then regester it in Application_Start using this code line:

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredExAttribute), typeof(RequiredExAttributeAdapter));
Flume answered 17/6, 2014 at 8:40 Comment(0)
O
2

Yes, it is possible using the MetadataType class, e.g:

[MetadataType(typeof(Base.Metadata))]
public class Base
{    
    public string RequiredProperty { get; set; }

    public class Metadata
    {
        [Required]
        public string RequiredProperty { get; set; }
    }
}

[MetadataType(typeof(Derived.Metadata))]
public class Derived : Base 
{
    public new class Metadata
    {
    }
}

And test it:

var type = typeof(Derived);

var metadataType = typeof(Derived.Metadata);

var provider = new AssociatedMetadataTypeTypeDescriptionProvider(type, metadataType);

TypeDescriptor.AddProviderTransparent(provider, type);

var instance = new Derived();

var results = new List<ValidationResult>();

Validator.TryValidateObject(instance,
    new ValidationContext(instance),
    results,
    true);

Debug.Assert(results.Count == 0);
Outlawry answered 13/3, 2017 at 15:3 Comment(0)
B
1

I tried Mahmoud's answer, but it didn't work for me without a few changes. Adding this as an answer so I can give the code that did in case it helps someone else, but full credit to Mahmoud Hboubati - I've upvoted your answer.

In my situation I had a base DTO class with a DbGeography property that was required for an MVC project which used a custom EditorTemplate and DisplayTemplate for the DbGeography type. But for posting a model to a Web API controller I wanted to have latitude/longitude fields added to a subclass of that DTO instead, which would be used to create and set an instance of a DbGeography class to set the value on the DbGeography property. Problem was, I couldn't make the DbGeography property not required on the subclass only.

When the boolean value was passed in the constructor using Mahmoud's approach, it never seemed to override the default value for me. That might be because I'm using Web API and registering the attribute using the factory approach, like below (in Global.asax.cs Application_Start method):

DataAnnotationsModelValidationFactory factory = (p, a) => new DataAnnotationsModelValidator(
    new List<ModelValidatorProvider>(), new RequiredExAttribute()
);

DataAnnotationsModelValidatorProvider provider = new DataAnnotationsModelValidatorProvider();
provider.RegisterAdapterFactory(typeof(RequiredExAttribute), factory);

I had to change the attribute class to this:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
...
public class RequiredExAttribute : RequiredAttribute
{
    public bool IsRequired { get; set; }

    public override bool IsValid(object value)
    {
        if (IsRequired)
            return base.IsValid(value);
        else
        {
            return true;
        }
    }

    public override bool RequiresValidationContext
    {
        get
        {
            return IsRequired;
        }
    }
}

public class RequiredExAttributeAdapter : RequiredAttributeAdapter
{
    public RequiredExAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredExAttribute attribute)
        : base(metadata, context, attribute) { }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        if (((RequiredExAttribute)Attribute).IsRequired)// required -> return normal required rules
            return base.GetClientValidationRules();
        else// not required -> return empty rules list
            return new List<ModelClientValidationRule>();
    }
}

Base Class:

[RequiredEx(IsRequired = true)]
public virtual DbGeography Location { get; set; }

Subclass:

[RequiredEx(IsRequired = false)]
public override DbGeography Location { get; set; }

[Required]
public decimal Latitude { get; set; }

[Required]
public decimal Longitude { get; set; }

Note, I used same method as Mahmoud did above for registering the attribute in my MVC project:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredExAttribute), typeof(RequiredExAttributeAdapter));
Bennington answered 10/11, 2016 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.