KEMBAR78
[API Proposal]: Expand data validation attributes · Issue #77402 · dotnet/runtime · GitHub
Skip to content

[API Proposal]: Expand data validation attributes #77402

@geeknoid

Description

@geeknoid

Background and motivation

Our project features hundreds of different option types against which we perform data validation operations. We've introduced a number of new data validation attributes which capture essential scenarios in our code. As these are general-purpose in nature, it would be great to add those to the core set of supported attributes.

API Proposal

namespace System.ComponentModel.Annotations;

public partial class RangeAttribute : ValidationAttribute
{
+    public bool IsLowerBoundExclusive { get; set; } = false;
+    public bool IsUpperBoundExclusive { get; set; } = false;
}

public partial class RequiredAttribute : ValidationAttribute
{
     // Fail validation for structs that equal the default value for the type
+    public bool DisallowDefaultValues { get; set; } = false;
}

// Validates that the specified string uses Base64 encoding
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+public class Base64StringAttribute : ValidationAttribute
+{
+}

// Specifies length ranges for string/IEnumerable
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+public class LengthAttribute : ValidationAttribute
+{
+    public LengthAttribute(int minLength = 0, int maxLength = -1);
+
+    public int MinLength { get; }
+    public int? MaxLength { get; }
+}

// Validation using allow and deny lists
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+public class AllowedValuesAttribute : ValidationAttribute
+{
+    public AllowedValuesAttribute (params object[] allowedValues);
+    public object[] AllowedValues { get; }
+}
+
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+public class DeniedValuesAttribute : ValidationAttribute
+{
+    public DeniedValuesAttribute(params object[] deniedValues);
+    public object[] DeniedValues { get; }
+}
Original API proposal
namespace System.ComponentModel.Annotations;

/// <summary>
/// Marks a property to be validated as <see href="https://en.wikipedia.org/wiki/Base64">Base64</see> string.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class Base64StringAttribute : ValidationAttribute
{
}

/// <summary>
/// Provides exclusive boundary validation for <see cref="long"/> or <see cref="double"/> values.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class ExclusiveRangeAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the minimum value for the range.
    /// </summary>
    public object Minimum { get; }

    /// <summary>
    /// Gets the maximum value for the range.
    /// </summary>
    public object Maximum { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="ExclusiveRangeAttribute"/> class.
    /// </summary>
    /// <param name="minimum">The minimum value, exclusive.</param>
    /// <param name="maximum">The maximum value, exclusive.</param>
    public ExclusiveRangeAttribute(int minimum, int maximum);

    /// <summary>
    /// Initializes a new instance of the <see cref="ExclusiveRangeAttribute"/> class.
    /// </summary>
    /// <param name="minimum">The minimum value, exclusive.</param>
    /// <param name="maximum">The maximum value, exclusive.</param>
    public ExclusiveRangeAttribute(double minimum, double maximum);
}

/// <summary>
/// Specifies the minimum length of any <see cref="IEnumerable"/> or <see cref="string"/> objects.
/// </summary>
/// <remarks>
/// The standard <see cref="MinLengthAttribute" /> supports only non generic <see cref="Array"/> or <see cref="string"/> typed objects
/// on .Net Framework, while <see cref="ICollection{T}"/> type is supported only on .Net Core.
/// See issue here <see href="https://github.com/dotnet/runtime/issues/23288"/>.
/// This attribute aims to allow validation of all these objects in a consistent manner across target frameworks.
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class LengthAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the minimum allowed length of the collection or string.
    /// </summary>
    public int MinimumLength { get; }

    /// <summary>
    /// Gets the maximum allowed length of the collection or string.
    /// </summary>
    public int? MaximumLength { get; }

    /// <summary>
    /// Gets or sets a value indicating whether the length validation should exclude the <see cref="MinimumLength"/> and <see cref="MaximumLength"/> values.
    /// </summary>
    /// <remarks>
    /// By default the property is set to <c>false</c>.
    /// </remarks>
    public bool Exclusive { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="LengthAttribute"/> class.
    /// </summary>
    /// <param name="minimumLength">
    /// The minimum allowable length of array/string data.
    /// Value must be greater than or equal to zero.
    /// </param>
    public LengthAttribute(int minimumLength);

    /// <summary>
    /// Initializes a new instance of the <see cref="LengthAttribute"/> class.
    /// </summary>
    /// <param name="minimumLength">
    /// The minimum allowable length of array/string data.
    /// Value must be greater than or equal to zero.
    /// </param>
    /// <param name="maximumLength">
    /// The maximum allowable length of array/string data.
    /// Value must be greater than or equal to zero.
    /// </param>
    public LengthAttribute(int minimumLength, int maximumLength);
}

/// <summary>
/// Provides a way of requiring a value that is not default(T) for a property of value type.
/// </summary>
/// <remarks>Only to be used with value type properties.</remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class NonDefaultValueRequiredAttribute : ValidationAttribute
{
}

/// <summary>
/// Provides a way of not accepting specific values for a property/field/parameter.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class RejectedValuesAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the values which are not accepted.
    /// </summary>
    public ICollection<object> RejectedValues { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="RejectedValuesAttribute"/> class.
    /// </summary>
    /// <param name="rejectedValues">Values which should not be accepted.</param>
    public RejectedValuesAttribute(params object[] rejectedValues);
}

/// <summary>
/// Provides a way of accepting only specific values for a property/field/parameter.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class AcceptedValuesAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the values which are accepted.
    /// </summary>
    public ICollection<object> AcceptedValues { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="AcceptedValuesAttribute"/> class.
    /// </summary>
    /// <param name="acceptedValues">Values which should be accepted.</param>
    public AcceptedValuesAttribute(params object[] acceptededValues);
}

/// <summary>
/// Provides boundary validation for <see cref="TimeSpan"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class TimeSpanAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the lower bound for time span.
    /// </summary>
    public TimeSpan Minimum { get; }

    /// <summary>
    /// Gets the upper bound for time span.
    /// </summary>
    public TimeSpan? Maximum { get; }

    /// <summary>
    /// Gets or sets a value indicating whether the time span validation should exclude the minimum and maximum values.
    /// </summary>
    /// <remarks>
    /// By default the property is set to <c>false</c>.
    /// </remarks>
    public bool Exclusive { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="minMs">Minimum in milliseconds.</param>
    public TimeSpanAttribute(int minMs);

    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="minMs">Minimum in milliseconds.</param>
    /// <param name="maxMs">Maximum in milliseconds.</param>
    public TimeSpanAttribute(int minMs, int maxMs);

    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="min">Minimum represented as time span string.</param>
    public TimeSpanAttribute(string min);

    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="min">Minimum represented as time span string.</param>
    /// <param name="max">Maximum represented as time span string.</param>
    public TimeSpanAttribute(string min, string max);
}

API Usage

class Options
{
    // require a valid base-64 string
    [Base64String]
    public string EncodedBinaryData { get; set; } = string.Empty;

    // a value between specific exclusive boundaries
    [ExclusiveRange(0, 1.0)]
    public double BetweenZeroAndOne { get; set; } = .5;

    // 1 to 5 items
    [Length(1, 5)]
    public int[] ArrayOfStuff { get; set; }

    // anything but these
    [RejectValues("Red")]
    public string Color { get; set; }

    // 10ms to 100ms
    [TimeSpan(10, 100)]
    public TimeSpan Timeout { get; set; }
}

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions