Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range attribute using dynamic values instead of fixed

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

like image 461
user1765862 Avatar asked Mar 07 '14 17:03

user1765862


2 Answers

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.

like image 154
BradleyDotNET Avatar answered Nov 02 '22 08:11

BradleyDotNET


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.

like image 45
pid Avatar answered Nov 02 '22 09:11

pid