Browse Source

Convert a typed range limit with the operand type of the attribute

Range(Type, string, string) keeps its limits as strings until the first
validation. They are converted the way the attribute converts them, so the
reported limit is the one the server validates against whatever the culture
of the request is, and a magnitude outside the decimal range survives it.
pull/26112/head
maliming 3 days ago
parent
commit
aee66f02a7
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 46
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs
  2. 23
      framework/test/Volo.Abp.Http.Tests/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel_Tests.cs

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

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
@ -11,6 +13,21 @@ namespace Volo.Abp.Http.Modeling;
[Serializable]
public class PropertyApiDescriptionModel
{
private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
{
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal)
};
public string Name { get; set; } = default!;
public string? JsonName { get; set; }
@ -73,24 +90,41 @@ public class PropertyApiDescriptionModel
}
// 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.
// A limit that already reads as invariant is kept verbatim, because rewriting it can
// only lose precision: "1e-30" would come back as a zero from a decimal round trip.
// first validation, so a numeric one is still written in the culture that declared it.
// Converting it with the operand type of the attribute keeps the api definition
// independent of the culture, and reports the value the attribute itself validates
// against, which is not always the value that was written down.
if (bound is string text)
{
if (decimal.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out _))
if (!NumericTypes.Contains(rangeAttribute.OperandType))
{
return text;
}
return decimal.TryParse(text, NumberStyles.Float, GetRangeLimitCulture(rangeAttribute), out var number)
? number.ToString(CultureInfo.InvariantCulture)
var converted = ConvertRangeLimitOrNull(text, rangeAttribute);
return converted != null
? Convert.ToString(converted, CultureInfo.InvariantCulture)
: text;
}
return Convert.ToString(bound, CultureInfo.InvariantCulture);
}
private static object? ConvertRangeLimitOrNull(string text, RangeAttribute rangeAttribute)
{
try
{
return TypeDescriptor
.GetConverter(rangeAttribute.OperandType)
.ConvertFromString(null, GetRangeLimitCulture(rangeAttribute), text);
}
catch (Exception)
{
// A limit the attribute can not convert itself is reported the way it was written.
return null;
}
}
private static CultureInfo GetRangeLimitCulture(RangeAttribute rangeAttribute)
{
#if NET8_0_OR_GREATER

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

@ -34,13 +34,25 @@ public class PropertyApiDescriptionModel_Tests
}
[Fact]
public void Create_Should_Keep_A_Typed_Range_Bound_That_Is_Already_Invariant()
public void Create_Should_Convert_A_Typed_Range_Bound_With_Its_Operand_Type()
{
// A decimal round trip would turn a magnitude below its range into a zero.
// A magnitude outside the decimal range is still a valid double bound.
var model = CreateModel(nameof(TestClass.ExponentRangeValue));
model.Minimum.ShouldBe("1e-30");
model.Maximum.ShouldBe("1e30");
model.Minimum.ShouldBe("1E-30");
model.Maximum.ShouldBe("1E+30");
}
[Fact]
public void Create_Should_Convert_A_Typed_Double_Range_Bound_Written_In_Another_Culture()
{
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de-DE")))
{
var model = CreateModel(nameof(TestClass.CultureExponentRangeValue));
model.Minimum.ShouldBe("1.5E+30");
model.Maximum.ShouldBe("9.5E+30");
}
}
[Fact]
@ -99,6 +111,9 @@ public class PropertyApiDescriptionModel_Tests
[Range(typeof(double), "1e-30", "1e30", ParseLimitsInInvariantCulture = true)]
public double ExponentRangeValue { get; set; }
[Range(typeof(double), "1,5E+30", "9,5E+30")]
public double CultureExponentRangeValue { get; set; }
[Range(typeof(DateTime), "2020-01-01", "2030-01-01")]
public DateTime DateRangeValue { get; set; }

Loading…
Cancel
Save