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).
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.
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...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With