Open eiriktsarpalis opened 1 year ago
Tagging subscribers to this area: @dotnet/area-system-runtime See info in area-owners.md if you want to be subscribed.
Author: | eiriktsarpalis |
---|---|
Assignees: | - |
Labels: | `api-suggestion`, `area-System.Runtime` |
Milestone: | Future |
Tagging subscribers to this area: @ajcvickers, @bricelam, @roji See info in area-owners.md if you want to be subscribed.
Author: | eiriktsarpalis |
---|---|
Assignees: | - |
Labels: | `api-suggestion`, `area-System.ComponentModel.DataAnnotations`, `area-System.Runtime` |
Milestone: | Future |
@eiriktsarpalis I'm inclined to pull this into .NET 8 to group it with the other DataAnnotations enhancements we're making during the release. @geeknoid Would you be able to use this if it was in .NET 8?
/cc @dotnet/area-system-componentmodel-dataannotations
Sure, marking as ready for review.
@jeffhandley Yes, I think we could use this in .NET 8
RangeAttribute<T>
that is abstract and constrains the parameters to have the same type.RangeAttribute<T>
doesn't extend RangeAttribute
such that we can constrain T
to IComparable<T>
rather than IComparable
.namespace System.ComponentModel.DataAnnotations;
public partial class RangeAttribute
{
// Existing:
// public RangeAttribute(int minimum, int maximum);
// public RangeAttribute(double minimum, double maximum);
private protected RangeAttribute(Type type, IComparable minimum, IComparable maximum);
}
public abstract class RangeAttribute<T> : RangeAttribute
where T: IComparable
{
protected RangeAttribute(T minimum, T maximum);
}
// Example usage:
//
// public class TimeSpanMillisecondRangeAttribute : RangeAttribute<TimeSpan>
// {
// public TimeSpanMillisecondRangeAttribute(int minimumMs, int maximumMs)
// : base(TimeSpan.FromMilliseconds(minimumMs),
// TimeSpan.FromMilliseconds(maximumMs))
// {
// }
// }
We will not pursue that revised design in .NET 8. Moving to Future and removing the https://github.com/dotnet/runtime/labels/blocking label.
Could we also use the IParsable
interface and create:
public sealed class ParsedRangeAttribute<T> : RangeAttribute<T> // this name could use some work
where T : IComparable, IParsable<T>
{
public ParsedRangeAttribute(string minimum, string maximum);
}
Then users wouldn't need to create their own derived attribute classes to use types that are IParsable, like TimeSpan.
Regarding naming, I think it would make more sense to use the Range<T>
attribute name for the sealed class - the one that would be used in all the callsites. So maybe it would make sense to say:
public abstract class ComparableRangeAttribute<T> : RangeAttribute
where T : IComparable
{
protected ComparableRangeAttribute(T minimum, T maximum);
}
public sealed class RangeAttribute<T> : ComparableRangeAttribute<T>
where T : IComparable, IParsable<T>
{
public RangeAttribute(string minimum, string maximum);
}
And then usages would look like:
public class ResilienceStrategyOptions
{
[Range<TimeSpan>("00:00:00", "1.00:00:00")]
public TimeSpan? MaxDelay { get; set; }
}
Background and motivation
The
RangeAttribute
currently supportsint
anddouble
ranges out of the box using dedicated constructor overloads, however any other range necessitates using this overload requiring the operand type as well as string formatted representations of the lower and upper bounds. This only works for types supported by theTypeConverter
class expressing limits in strings forces concerns around culture-sensitive formatting:I've been working on a prototype that adds a protected constructor which accepts arbitrary
IComparable
bounds directly.API Proposal
API Usage
The above example is now rendered as follows:
Alternative Designs
protected
, since marking it public would add OOTB support for things like string or long ranges using their built-in IComparable implementation. Marking itprotected
emphasizes its use as an extensibility point for user-defined derived validation attributes. If people thinks it is useful, we could mark aspublic
instead.IComparer
support since the validator implementation is oriented around handling ofIComparable
values.Risks
No response
cc @geeknoid @jeffhandley