Browse Source

Write the typed range limits with the invariant culture

Range(Type, string, string) keeps its limits as strings until the first
validation, so a numeric limit was reported in the culture that declared it
and could not be merged with a FluentValidation bound.
pull/26112/head
maliming 6 days ago
parent
commit
0ed8624e7a
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 32
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs
  2. 4
      framework/test/Volo.Abp.Http.FluentValidation.Tests/Volo/Abp/Http/FluentValidation/FluentValidationApiDescription_Tests.cs
  3. 4
      framework/test/Volo.Abp.Http.FluentValidation.Tests/Volo/Abp/Http/FluentValidation/TestObjects/CultureTestDto.cs
  4. 27
      framework/test/Volo.Abp.Http.Tests/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel_Tests.cs

32
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs

@ -55,8 +55,8 @@ public class PropertyApiDescriptionModel
TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType),
IsRequired = customAttributes.OfType<RequiredAttribute>().Any() || propertyInfo.GetCustomAttributesData().Any(attr => attr.AttributeType.Name == "RequiredMemberAttribute"),
IsNullable = ReflectionHelper.IsNullable(propertyInfo),
Minimum = rangeAttribute != null ? Convert.ToString(rangeAttribute.Minimum, CultureInfo.InvariantCulture) : null,
Maximum = rangeAttribute != null ? Convert.ToString(rangeAttribute.Maximum, CultureInfo.InvariantCulture) : null,
Minimum = GetRangeBound(rangeAttribute, rangeAttribute?.Minimum),
Maximum = GetRangeBound(rangeAttribute, rangeAttribute?.Maximum),
MinimumIsExclusive = GetMinimumIsExclusive(rangeAttribute),
MaximumIsExclusive = GetMaximumIsExclusive(rangeAttribute),
MinLength = customAttributes.OfType<MinLengthAttribute>().FirstOrDefault()?.Length ?? customAttributes.OfType<StringLengthAttribute>().FirstOrDefault()?.MinimumLength,
@ -65,6 +65,34 @@ public class PropertyApiDescriptionModel
};
}
private static string? GetRangeBound(RangeAttribute? rangeAttribute, object? bound)
{
if (rangeAttribute == null || bound == null)
{
return null;
}
// The Range(Type, string, string) constructor keeps its limits as strings until the
// first validation, so a numeric one is written in the culture of the declaring code.
if (bound is string text)
{
return decimal.TryParse(text, NumberStyles.Float, GetRangeLimitCulture(rangeAttribute), out var number)
? number.ToString(CultureInfo.InvariantCulture)
: text;
}
return Convert.ToString(bound, CultureInfo.InvariantCulture);
}
private static CultureInfo GetRangeLimitCulture(RangeAttribute rangeAttribute)
{
#if NET8_0_OR_GREATER
return rangeAttribute.ParseLimitsInInvariantCulture ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture;
#else
return CultureInfo.CurrentCulture;
#endif
}
private static bool? GetMinimumIsExclusive(RangeAttribute? rangeAttribute)
{
if (rangeAttribute == null)

4
framework/test/Volo.Abp.Http.FluentValidation.Tests/Volo/Abp/Http/FluentValidation/FluentValidationApiDescription_Tests.cs

@ -246,6 +246,10 @@ public class FluentValidationApiDescription_Tests : AbpHttpFluentValidationTestB
property.Minimum.ShouldBe("2");
property.Maximum.ShouldBe("9.5");
var typed = await GetPropertyAsync<CultureTestDto>(nameof(CultureTestDto.TypedDecimalRangeValue));
typed.Minimum.ShouldBe("2");
typed.Maximum.ShouldBe("9.5");
}
}

4
framework/test/Volo.Abp.Http.FluentValidation.Tests/Volo/Abp/Http/FluentValidation/TestObjects/CultureTestDto.cs

@ -9,6 +9,9 @@ public class CultureTestDto
[Range(1.5, 9.5)]
public double DecimalRangeValue { get; set; }
[Range(typeof(decimal), "1,5", "9,5")]
public decimal TypedDecimalRangeValue { get; set; }
[Range(typeof(DateTime), "2020-01-01", "2030-01-01")]
public DateTime DateRangeValue { get; set; }
}
@ -18,5 +21,6 @@ public class CultureTestDtoValidator : AbstractValidator<CultureTestDto>
public CultureTestDtoValidator()
{
RuleFor(x => x.DecimalRangeValue).GreaterThanOrEqualTo(2.0);
RuleFor(x => x.TypedDecimalRangeValue).GreaterThanOrEqualTo(2m);
}
}

27
framework/test/Volo.Abp.Http.Tests/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel_Tests.cs

@ -21,6 +21,27 @@ public class PropertyApiDescriptionModel_Tests
}
}
[Fact]
public void Create_Should_Write_Typed_Range_Bounds_With_The_Invariant_Culture()
{
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de-DE")))
{
var model = CreateModel(nameof(TestClass.TypedDecimalRangeValue));
model.Minimum.ShouldBe("1.5");
model.Maximum.ShouldBe("9.5");
}
}
[Fact]
public void Create_Should_Keep_A_Typed_Range_Bound_That_Is_Not_A_Number()
{
var model = CreateModel(nameof(TestClass.DateRangeValue));
model.Minimum.ShouldBe("2020-01-01");
model.Maximum.ShouldBe("2030-01-01");
}
[Fact]
public void Create_Should_Read_The_Exclusive_Bounds_Of_The_Range_Attribute()
{
@ -62,6 +83,12 @@ public class PropertyApiDescriptionModel_Tests
[Range(1.5, 9.5)]
public double DecimalRangeValue { get; set; }
[Range(typeof(decimal), "1,5", "9,5")]
public decimal TypedDecimalRangeValue { get; set; }
[Range(typeof(DateTime), "2020-01-01", "2030-01-01")]
public DateTime DateRangeValue { get; set; }
[Range(1, 100, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public int ExclusiveRangeValue { get; set; }

Loading…
Cancel
Save