7

I want to use Data annotations Range attribute inside my mvc viewmodel. Problem is that this range attributes should be dynamic values.

My viewmodel has also ValueOne and ValueTwo properties. Based on this values I want to set Range attr. values like

 [Range(1, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}.")]

Where 1 and 1000 should be replaces with ValueOne and ValueTwo property values.

so I tried with custom ValidateCustomAttribute

public class ValidateCustomAttribute: ValidationAttribute
    {
        private readonly double _MinValue = 0;
        private readonly double _MaxValue = 100;

        public override bool IsValid(object value)
        {
            double val = (double)value;
            return val >= _MinValue && val <= _MaxValue;
        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format(ErrorMessage, _MinValue, _MaxValue);
        }
    }

how can I replace this

private readonly double _MinValue = 0;
private readonly double _MaxValue = 100;

with dynamic values (ValueOne and ValueTwo from my viewmodel).

user1765862
  • 13,635
  • 28
  • 115
  • 220

3 Answers3

6

This can't be done, you can't have variables in attributes. Attribute values must be known at compile time.

See this question/answer and this one.

Community
  • 1
  • 1
BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
4

Just add a constructor:

private double _MinValue, _MaxValue; // no readonly keyword

public ValidateCustomAttribute(double min, double max, Func<string> errorMessageAccessor)
    : base(errorMessageAccessor)
{
    _MinValue = min;
    _MaxValue = max;
}

What you can't do is have variables in the attribute constructor invokation.

This is not possible:

[ValidateCustom(min, max)]

But if you use literals (or constants) in your code you can have these:

[ValidateCustom(1, 1000)]

And on another class or method:

[ValidateCustom(3, 45)]

What you are missing is the constructor taking in those static values and affixing them to the construct you are describing with your attribute.


EDIT: The ugly way around this

If you really really need this, you can circumvent the limitation but it is as ugly as it can get. I am STRONGLY against this, but you asked for it...

  1. categorize your data
  2. map categories to binding symbols
  3. use binding symbols
  4. resolve binding symbols to data

So, let's get to work:

1) categorize your data

Say, your data is a range (min, max), the first thing to do is establish which values are possible, let's say you have 4 possible ranges (may be hundreds, but that's anther problem altogether).

(1, 1000)
(10, 20)
(3, 45)
(5, 7)

2) map categories to binding symbols

Now you have to use an enum as binding symbols for those ranges:

public enum MyRanges
{
    R1, R2, R3, R4
}

3) use binding symbols

Define the constructor as taking in the binding symbol:

private MyRanges _R;

public ValidateCustomAttribute(MyRanges r, Func<string> errorMessageAccessor)
    : base(errorMessageAccessor)
{
    _R = r;
}

The attribute will be used like this:

[ValidateCustom(MyRanges.R2, "ERROR!")]

4) resolve binding symbols to data

The last you need is a dictionary with the actual data:

Dictionary<MyRanges, double> dataMin = {
    { MyRanges.R1, 1},
    { MyRanges.R2, 10},
    { MyRanges.R3, 3},
    { MyRanges.R4, 5}
};

Dictionary<MyRanges, double> dataMax = {
    { MyRanges.R1, 1000},
    { MyRanges.R2, 20},
    { MyRanges.R3, 45},
    { MyRanges.R4, 7}
};

The test will use the binding this way:

public override bool IsValid(object value)
{
    double val = (double)value;
    return val >= dataMin[_R] && val <= dataMax[_R]; // get data through binding
}

Now you can change behind the scenes those values and all attributes bound by the binding symbols behave differently:

dataMax[MyRanges.R4] = 29;

Done.

What cannot change is now the binding from attribute to category, but the data contained in the category is free to change.

But ugly and impossible to maintain. Don't do it, really.

pid
  • 11,472
  • 6
  • 34
  • 63
  • I dont understand, [ValidateCustom(1, 1000)] not doing exactly what I want. This is again as Range with standard values, I need range with dynamic values. Please elaborate more if I'm wrong. – user1765862 Mar 07 '14 at 17:49
  • Attributes are evaluated (executed) when an assembly is loaded into memory. From then on the metadata cannot change (that would be meta-meta-programming). You can circumvent this problem but it is ugly. I'll show you in an edit in 5 mins. – pid Mar 07 '14 at 17:52
-1

You can do it like this:

public class MinimumAgeAttribute : RangeAttribute
{
    public static string MinimumValue => ConfigurationManager.AppSettings["your key"];
    public static string MaxValue => ConfigurationManager.AppSettings["your key"];

    public CustomRangeAttribute(Type type):base(type,MaxValue , MinimumValue)
    {
    }

}

And after this you have to register the attribute in Global.asax like this:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomRangeAttribute),
            typeof(RangeAttributeAdapter));

After this you can use it like this:

[CustomRange(typeof(DateTime), ErrorMessage = "You message")]
public DateTime DateOfBirth { get; set; }
Amro Mustafa
  • 589
  • 4
  • 15