简体   繁体   中英

Can I use a Model property as part of a Range validation attribute

In my model I have an object that has the following property.

[Range(typeof(int), "2014", "2024", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

The lower and upper range values are 2014 and 2024 respectively. However, rather than use these fixed values, I'd like them to be based on another property in the model.

So, for example, if I had a property, CurrentFiscalYear , my hypothetical Range attribute would look like this.

[Range(typeof(int), CurrentFiscalYear, CurrentFiscalYear + 10, ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

Is something like this possible? Or must the lower and upper values be provided at compile time?

No, this isn't possible. Attribute parameter values just be "compile-time constant" values. In other words, the actual value of the parameter must be known when you compile the program.

From MSDN - Attributes tutorial :

Attribute parameters are restricted to constant values of the following types:

  • Simple types (bool, byte, char, short, int, long, float, and double)
  • string
  • System.Type
  • enums
  • object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
  • One-dimensional arrays of any of the above types

This is documentation for .NET 1.1, but has not changed.

Workaround

This isn't tested at all but you can create a custom ValidationAttribute which takes the range and also model property names who's values to add to the range values when testing for validity. You can create an internal standard RangeAttribute to do the work for you and even keep client validation working by implementing IClientValidatable :

public sealed class ShiftedRangeAttribute : ValidationAttribute
{
    public string MinShiftProperty { get; private set; }
    public string MaxShiftProperty { get; private set; }

    public double Minimum { get; private set; }
    public double Maximum { get; private set; }

    public ShiftedRangeAttribute(double minimum, double maximum, string minShiftProperty, string maxShiftProperty)
    {
        this.Minimum = minimum;
        this.Maximum = maximum;
        this.MinShiftProperty = minShiftProperty;
        this.MaxShiftProperty = maxShiftProperty;
    }

    public ShiftedRangeAttribute(int minimum, int maximum, string minShiftProperty, string maxShiftProperty)
    {
        this.Minimum = minimum;
        this.Maximum = maximum;
        this.MinShiftProperty = minShiftProperty;
        this.MaxShiftProperty = maxShiftProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        RangeAttribute attr = this.CreateRangeAttribute(validationContext.ObjectInstance);
        return attr.GetValidationResult(value, validationContext);
    }

    internal RangeAttribute CreateRangeAttribute(object model)
    {
        double min = this.Minimum;
        if (this.MinShiftProperty != null)
        {
            min += Convert.ToDouble(model.GetType().GetProperty(this.MinShiftProperty).GetValue(model));
        }

        double max = this.Maximum;
        if (this.MaxShiftProperty != null)
        {
            max += Convert.ToDouble(model.GetType().GetProperty(this.MaxShiftProperty).GetValue(model));
        }

        return new RangeAttribute(min, max);
    }
}

If you want it to work with client validation, you will also to create a DataAnnotationsModelValidator and register it in your global.asax Application_Start() to ensure the client validation HTML attributes are output. Again you can cheat and use the built-in RangeAttributeAdapter to help you because in Javascript it is ultimately just a range validator:

public class ShiftedRangeAttributeAdapter : DataAnnotationsModelValidator<ShiftedRangeAttribute>
{
    public ShiftedRangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, ShiftedRangeAttribute attribute)
        : base(metadata, context, attribute)
    {
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        RangeAttribute attr = this.Attribute.CreateRangeAttribute(this.Metadata.Container);
        return new RangeAttributeAdapter(this.Metadata, this.ControllerContext, attr).GetClientValidationRules();
    }
}

...

DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof(ShiftedRangeAttribute), typeof(ShiftedRangeAttributeAdapter));

Note that the client validation code only works if the class containing the properties is the top-level model class, which is stored in Metadata.Container . You cannot access the "parent" of the current property. You would need to do more work to create a custom jQuery validator to handle this properly.

You can then use it as so:

[ShiftedRange(0, 10, "CurrentFiscalYear", "CurrentFiscalYear", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

EDIT: fixed some bugs after testing

This can be done by writing a custom ValidationAttribute, implementation could be done something like this:

public sealed class FiscalYearAttribute : ValidationAttribute
{
    public string CurrentFiscalYear { get; set; }

    public override bool IsValid(object value)
    {
        var currentFiscalYearString = HttpContext.Current.Request[CurrentFiscalYear];
        var currentFiscalYear = int.Parse(currentFiscalYearString);
        var fiscalYear = (int) value;

        return fiscalYear >=  currentFiscalYear && fiscalYear <= currentFiscalYear + 10;
    }

    public override string FormatErrorMessage(string name)
    {
        return name + " error description here.";
    }
}

Usage:

[Required]
[Display(Name = "CurrentFiscalYear")]
public int CurrentFiscalYear { get; set; }

[Display(Name = "FiscalYear")]
[FiscalYear(CurrentFiscalYear = "CurrentFiscalYear")]
public int FiscalYear { get; set; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM