System.ComponentModel.DataAnnotations offers a RangeAttribute that can be used to validate that a value is within a given range. Silverlight 3’s SDK includes this assembly and .NET RIA Services and ASP.NET Dynamic Data use this too.
Something that a few of us have stumbled on here internally is that the RangeAttribute doesn’t have a constructor that takes Decimal values. In fact, a bug was submitted by another feature crew for this. Unfortunately, even if RangeAttribute offered a constructor that accepted decimals, you still wouldn’t be able to pass them in.
Here’s what the code would look like:
1: public class MyClass
2: {
3: [Range(0.5m, 1.0m)]
4: public decimal MyValue { get; set; }
5: }
But that results in compile errors in C#:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
And you get different compile errors in VB.NET:
Attribute constructor has a parameter of type 'Decimal', which is not an integral, floating-point or Enum type or one of Object, Char, String, Boolean, System.Type or 1-dimensional array of these types.
Decimal isn’t a primitive CLR type and attribute constructor parameters must be primitive CLR types representing constant values. So we closed the bug as working as designed and RangeAttribute doesn’t have a constructor that accepts decimals.
The two workarounds that came to mind for this problem, but they both admittedly have their concessions.
- Use doubles. Simply don’t suffix the value with “m” and it will be passed in as a double, and the RangeAttribute will be happy.
- The concession here is that you lose some precision
- I was able to get 0.49999999999999996 to pass itself off as within this range of 0.5 to 1.0
- Use strings. You can supply the RangeAttribute with a Type and 2 strings, and those strings will be converted to the specified type. So you could supply typeof(decimal) and “0.5” and “1.0” as your values.
- This is subject to localization issues in Silverlight. Even though you wrote the code in your language, the attribute value will be converted to decimal using the runtime culture.
- There’s really no workaround for this and you can never guarantee that an end user won’t have their culture set to something you don’t expect.
Because of the localization issue with #2, I suggest using #1. It looks cleaner too. While it does run the risk of some precision loss, I would expect that the value is going to be rounded off before getting stored anyway.