| File: src\Shared\Data.Validation\LengthAttribute.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj (Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Reflection; #pragma warning disable CA1716 namespace Microsoft.Shared.Data.Validation; #pragma warning restore CA1716 /// <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="System.Collections.Generic.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> #if !SHARED_PROJECT [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] #endif [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] internal 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> /// <value> /// The default value is <c>false</c>. /// </value> 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> [RequiresUnreferencedCode("Uses reflection to get the 'Count' property on types that don't implement ICollection. This 'Count' property may be trimmed. Ensure it is preserved.")] public LengthAttribute(int minimumLength) { MinimumLength = minimumLength; MaximumLength = null; } /// <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> [RequiresUnreferencedCode("Uses reflection to get the 'Count' property on types that don't implement ICollection. This 'Count' property may be trimmed. Ensure it is preserved.")] public LengthAttribute(int minimumLength, int maximumLength) { MinimumLength = minimumLength; MaximumLength = maximumLength; } /// <summary> /// Validates that a given value is in range. /// </summary> /// <remarks> /// This method returns <c>true</c> if the <paramref name = "value" /> is <see langword="null"/>. /// It is assumed the <see cref = "RequiredAttribute" /> is used if the value may not be <see langword="null"/>. /// </remarks> /// <param name="value">The value to validate.</param> /// <param name="validationContext">Additional context for this validation.</param> /// <returns>A value indicating success or failure.</returns> /// <exception cref="ArgumentOutOfRangeException"><see cref="MinimumLength"/> is less than zero or if it is greater than <see cref="MaximumLength"/>.</exception> /// <exception cref="InvalidOperationException">The validated type is not supported.</exception> [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The ctor is marked with RequiresUnreferencedCode.")] protected override ValidationResult IsValid(object? value, ValidationContext? validationContext) { if (MinimumLength < 0) { throw new InvalidOperationException($"{nameof(LengthAttribute)} requires a minimum length >= 0 (see field {validationContext.GetDisplayName()})"); } if (MaximumLength.HasValue && MinimumLength >= MaximumLength) { throw new InvalidOperationException($"{nameof(LengthAttribute)} requires the minimum length to be less than maximum length (see field {validationContext.GetDisplayName()})"); } // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null. if (value == null) { return ValidationResult.Success!; } int count; switch (value) { case string s: count = s.Length; break; case ICollection c: count = c.Count; break; case IEnumerable e: count = 0; foreach (var item in e) { count++; } break; default: var property = GetCountProperty(value); if (property != null && property.CanRead && property.PropertyType == typeof(int)) { count = (int)property.GetValue(value)!; } else { throw new InvalidOperationException($"{nameof(LengthAttribute)} is not supported for fields of type {value.GetType()} (see field {validationContext.GetDisplayName()})"); } break; } return Validate(count, validationContext); } [RequiresUnreferencedCode("Uses reflection to get the 'Count' property on types that don't implement ICollection. This 'Count' property may be trimmed. Ensure it is preserved.")] private static PropertyInfo? GetCountProperty(object value) => value.GetType().GetRuntimeProperty("Count"); private ValidationResult Validate(int count, ValidationContext? validationContext) { bool result; if (MaximumLength.HasValue) { // Minimum and maximum length validation. result = Exclusive ? count > MinimumLength && count < MaximumLength : count >= MinimumLength && count <= MaximumLength; } else { // Minimum length validation only. result = Exclusive ? count > MinimumLength : count >= MinimumLength; } if (!result) { if (!string.IsNullOrEmpty(ErrorMessage) || !string.IsNullOrEmpty(ErrorMessageResourceName)) { return new ValidationResult(FormatErrorMessage(validationContext.GetDisplayName()), validationContext.GetMemberName()); } var exclusiveString = Exclusive ? "exclusive " : string.Empty; var orEqualString = Exclusive ? string.Empty : "or equal "; var validationMessage = MaximumLength.HasValue ? $"The field {validationContext.GetDisplayName()} length must be in the {exclusiveString}range [{MinimumLength}..{MaximumLength}]." : $"The field {validationContext.GetDisplayName()} length must be greater {orEqualString}than {MinimumLength}."; return new ValidationResult(validationMessage, validationContext.GetMemberName()); } return ValidationResult.Success!; } }