Skip to content

Commit 5a5a95c

Browse files
committed
Limit types supported by NullableConverterFactory
1 parent e215183 commit 5a5a95c

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

src/Components/Endpoints/src/FormMapping/Converters/NullableConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public bool TryConvertValue(ref FormDataReader reader, string value, out T? resu
4141
[RequiresUnreferencedCode(FormMappingHelpers.RequiresUnreferencedCodeMessage)]
4242
internal override bool TryRead(ref FormDataReader reader, Type type, FormDataMapperOptions options, out T? result, out bool found)
4343
{
44-
// Donot call non-nullable converter's TryRead method, it will fail to parse empty
44+
// Do not call non-nullable converter's TryRead method, it will fail to parse empty
4545
// string. Call the TryConvertValue method above (similar to ParsableConverter) so
4646
// that it can handle the empty string correctly
4747
found = reader.TryGetValue(out var value);

src/Components/Endpoints/src/FormMapping/Factories/NullableConverterFactory.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ internal sealed class NullableConverterFactory : IFormDataConverterFactory
1515
public bool CanConvert(Type type, FormDataMapperOptions options)
1616
{
1717
var underlyingType = Nullable.GetUnderlyingType(type);
18-
return underlyingType != null && options.ResolveConverter(underlyingType) != null;
18+
19+
if (underlyingType == null)
20+
{
21+
return false;
22+
}
23+
24+
return IsSupportedUnderlyingType(underlyingType) && options.ResolveConverter(underlyingType) != null;
1925
}
2026

2127
[RequiresDynamicCode(FormMappingHelpers.RequiresDynamicCodeMessage)]
@@ -24,6 +30,7 @@ public FormDataConverter CreateConverter(Type type, FormDataMapperOptions option
2430
{
2531
var underlyingType = Nullable.GetUnderlyingType(type);
2632
Debug.Assert(underlyingType != null);
33+
Debug.Assert(IsSupportedUnderlyingType(underlyingType));
2734

2835
var underlyingConverter = options.ResolveConverter(underlyingType);
2936
Debug.Assert(underlyingConverter != null);
@@ -34,4 +41,14 @@ public FormDataConverter CreateConverter(Type type, FormDataMapperOptions option
3441
return Activator.CreateInstance(expectedConverterType, underlyingConverter) as FormDataConverter ??
3542
throw new InvalidOperationException($"Unable to create converter for type '{type}'.");
3643
}
44+
45+
private static bool IsSupportedUnderlyingType(Type type)
46+
{
47+
return Type.GetTypeCode(type) != TypeCode.Object || IsSupportedUnderlyingObjectType(type);
48+
}
49+
50+
private static bool IsSupportedUnderlyingObjectType(Type type)
51+
{
52+
return type == typeof(DateOnly) || type == typeof(TimeOnly) || type == typeof(DateTimeOffset);
53+
}
3754
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using Microsoft.AspNetCore.Components.Endpoints.FormMapping;
3+
4+
namespace Microsoft.AspNetCore.Components.Endpoints.Tests.FormMapping.Factories;
5+
6+
public class NullableConverterFactoryTests
7+
{
8+
[Fact]
9+
public void CanConvert_ReturnsFalseForNonParsableStruct()
10+
{
11+
var factory = NullableConverterFactory.Instance;
12+
var options = new FormDataMapperOptions();
13+
var result = factory.CanConvert(typeof(NonParsableTestStruct?), options);
14+
Assert.False(result);
15+
}
16+
17+
[Fact]
18+
public void CanConvert_ReturnsFalseForParsableStruct()
19+
{
20+
var factory = NullableConverterFactory.Instance;
21+
var options = new FormDataMapperOptions();
22+
var result = factory.CanConvert(typeof(ParsableTestStruct?), options);
23+
Assert.False(result);
24+
}
25+
26+
[Theory]
27+
[InlineData(typeof(DateOnly?))]
28+
[InlineData(typeof(TimeOnly?))]
29+
[InlineData(typeof(DateTimeOffset?))]
30+
public void CanConvert_ReturnsTrueForSupportedObjectTypes(Type type)
31+
{
32+
var factory = NullableConverterFactory.Instance;
33+
var options = new FormDataMapperOptions();
34+
var result = factory.CanConvert(type, options);
35+
Assert.True(result);
36+
}
37+
38+
private struct NonParsableTestStruct
39+
{
40+
}
41+
42+
private struct ParsableTestStruct : IParsable<ParsableTestStruct>
43+
{
44+
public static ParsableTestStruct Parse(string s, IFormatProvider provider)
45+
{
46+
throw new NotImplementedException();
47+
}
48+
49+
public static bool TryParse([NotNullWhen(true)] string s, IFormatProvider provider, [MaybeNullWhen(false)] out ParsableTestStruct result)
50+
{
51+
throw new NotImplementedException();
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)