-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Add a RangeAttribute constructor supporting arbitrary IComparable ranges. #82526
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsBackground and motivationThe public class TimeSpanRangeAttribute : RangeAttribute
{
public TimeSpanRangeAttribute(int minMilliseconds, int maxMilliseconds)
: base(type: typeof(TimeSpan),
minimum: TimeSpan.FromMilliseconds(minMilliseconds).ToString("c"),
maximum: TimeSpan.FromMilliseconds(maxMilliseconds).ToString("c"))
{
ParseLimitsInInvariantCulture = true;
}
} I've been working on a prototype that adds a protected constructor which accepts arbitrary API Proposalnamespace System.ComponentModel.DataAnnotations;
public partial class RangeAttribute
{
public RangeAttribute(int minimum, int maximum);
public RangeAttribute(double minimum, double maximum);
+ protected RangeAttribute(IComparable minimum, IComparable maximum);
[RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated.")]
public RangeAttribute(Type type, string minimum, string maximum);
} API UsageThe above example is now rendered as follows: public class TimeSpanMillisecondRangeAttribute : RangeAttribute
{
public TimeSpanMillisecondRangeAttribute(int minimumMs, int maximumMs)
: base(TimeSpan.FromMilliseconds(minimumMs), TimeSpan.FromMilliseconds(maximumMs))
{ }
} Alternative Designs
RisksNo response
|
Tagging subscribers to this area: @ajcvickers, @bricelam, @roji Issue DetailsBackground and motivationThe public class TimeSpanRangeAttribute : RangeAttribute
{
public TimeSpanRangeAttribute(int minMilliseconds, int maxMilliseconds)
: base(type: typeof(TimeSpan),
minimum: TimeSpan.FromMilliseconds(minMilliseconds).ToString("c"),
maximum: TimeSpan.FromMilliseconds(maxMilliseconds).ToString("c"))
{
ParseLimitsInInvariantCulture = true;
}
} I've been working on a prototype that adds a protected constructor which accepts arbitrary API Proposalnamespace System.ComponentModel.DataAnnotations;
public partial class RangeAttribute
{
public RangeAttribute(int minimum, int maximum);
public RangeAttribute(double minimum, double maximum);
+ protected RangeAttribute(IComparable minimum, IComparable maximum);
[RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated.")]
public RangeAttribute(Type type, string minimum, string maximum);
} API UsageThe above example is now rendered as follows: public class TimeSpanMillisecondRangeAttribute : RangeAttribute
{
public TimeSpanMillisecondRangeAttribute(int minimumMs, int maximumMs)
: base(TimeSpan.FromMilliseconds(minimumMs), TimeSpan.FromMilliseconds(maximumMs))
{ }
} Alternative Designs
RisksNo response
|
@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 |
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
blocking
|
Could we also use the 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 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
namespace System.ComponentModel.DataAnnotations; public partial class RangeAttribute { public RangeAttribute(int minimum, int maximum); public RangeAttribute(double minimum, double maximum); + public RangeAttribute(IComparable minimum, IComparable maximum); [RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated.")] public RangeAttribute(Type type, string minimum, string maximum); }
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
The text was updated successfully, but these errors were encountered: