Skip to content

Commit b0daf16

Browse files
committed
Binding factories and tests
1 parent 878f68c commit b0daf16

File tree

8 files changed

+369
-34
lines changed

8 files changed

+369
-34
lines changed

src/Components/Endpoints/src/Binding/DefaultFormValuesSupplier.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ internal class DefaultFormValuesSupplier : IFormValueSupplier
2020
BindingFlags.NonPublic | BindingFlags.Static) ??
2121
throw new InvalidOperationException($"Unable to find method '{nameof(DeserializeCore)}'.");
2222

23-
private readonly FormDataProvider _formData;
23+
private readonly HttpContextFormDataProvider _formData;
2424
private readonly FormDataSerializerOptions _options = new();
2525
private static readonly ConcurrentDictionary<Type, Func<IReadOnlyDictionary<string, StringValues>, FormDataSerializerOptions, string, object>> _cache =
2626
new();
2727

2828
public DefaultFormValuesSupplier(FormDataProvider formData)
2929
{
30-
_formData = formData;
30+
_formData = (HttpContextFormDataProvider)formData;
3131
}
3232

3333
public bool CanBind(string formName, Type valueType)
@@ -67,7 +67,9 @@ private Func<IReadOnlyDictionary<string, StringValues>, FormDataSerializerOption
6767

6868
private static object? DeserializeCore<T>(IReadOnlyDictionary<string, StringValues> form, FormDataSerializerOptions options, string value)
6969
{
70-
// Culture needs to come from the request.
70+
// Form values are parsed according to the culture of the request, which is set to the current culture by the localization middleware.
71+
// Some form input types use the invariant culture when sending the data to the server. For those cases, we'll
72+
// provide a way to override the culture to use to parse that value.
7173
var reader = new FormDataReader(form, CultureInfo.CurrentCulture);
7274
reader.PushPrefix(value);
7375
return FormDataDeserializer.Deserialize<T>(reader, options);
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -7,3 +7,10 @@ internal abstract class FormDataConverter<T> : FormDataConverter
77
{
88
internal abstract bool TryRead(ref FormDataReader context, Type type, FormDataSerializerOptions options, out T? result, out bool found);
99
}
10+
11+
internal interface IFormDataConverterFactory
12+
{
13+
public static abstract bool CanConvert(Type type, FormDataSerializerOptions options);
14+
15+
public static abstract FormDataConverter CreateConverter(Type type, FormDataSerializerOptions options);
16+
}
Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Concurrent;
5+
46
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
57

68
internal class FormDataSerializerOptions
79
{
8-
private readonly Dictionary<Type, FormDataConverter> _converters = new();
10+
private readonly ConcurrentDictionary<Type, FormDataConverter> _converters = new();
11+
private readonly List<Func<Type, FormDataSerializerOptions, FormDataConverter?>> _factories = new();
912

1013
public FormDataSerializerOptions()
1114
{
12-
_converters.Add(typeof(string), new ParsableConverter<string>());
13-
_converters.Add(typeof(char), new ParsableConverter<char>());
14-
_converters.Add(typeof(bool), new ParsableConverter<bool>());
15-
_converters.Add(typeof(byte), new ParsableConverter<byte>());
16-
_converters.Add(typeof(sbyte), new ParsableConverter<sbyte>());
17-
_converters.Add(typeof(ushort), new ParsableConverter<ushort>());
18-
_converters.Add(typeof(uint), new ParsableConverter<uint>());
19-
_converters.Add(typeof(ulong), new ParsableConverter<ulong>());
20-
_converters.Add(typeof(Int128), new ParsableConverter<Int128>());
21-
_converters.Add(typeof(short), new ParsableConverter<short>());
22-
_converters.Add(typeof(int), new ParsableConverter<int>());
23-
_converters.Add(typeof(long), new ParsableConverter<long>());
24-
_converters.Add(typeof(UInt128), new ParsableConverter<UInt128>());
25-
_converters.Add(typeof(Half), new ParsableConverter<Half>());
26-
_converters.Add(typeof(float), new ParsableConverter<float>());
27-
_converters.Add(typeof(double), new ParsableConverter<double>());
28-
_converters.Add(typeof(decimal), new ParsableConverter<decimal>());
29-
_converters.Add(typeof(DateOnly), new ParsableConverter<DateOnly>());
30-
_converters.Add(typeof(DateTime), new ParsableConverter<DateTime>());
31-
_converters.Add(typeof(DateTimeOffset), new ParsableConverter<DateTimeOffset>());
32-
_converters.Add(typeof(TimeSpan), new ParsableConverter<TimeSpan>());
33-
_converters.Add(typeof(TimeOnly), new ParsableConverter<TimeOnly>());
34-
_converters.Add(typeof(Guid), new ParsableConverter<Guid>());
15+
_converters = new(WellKnownConverters.Converters);
16+
17+
_factories.Add((type, options) => ParsableConverterFactory.CanConvert(type, options) ? ParsableConverterFactory.CreateConverter(type, options) : null);
18+
_factories.Add((type, options) => NullableConverterFactory.CanConvert(type, options) ? NullableConverterFactory.CreateConverter(type, options) : null);
3519
}
3620

3721
internal bool HasConverter(Type valueType) => _converters.ContainsKey(valueType);
@@ -46,9 +30,19 @@ internal FormDataConverter<T> ResolveConverter<T>()
4630
{
4731
if (!_converters.TryGetValue(typeof(T), out var converter))
4832
{
49-
throw new InvalidOperationException($"No converter registered for type '{typeof(T)}'.");
33+
throw new InvalidOperationException($"No converter registered for type '{typeof(T).FullName}'.");
5034
}
5135

5236
return (FormDataConverter<T>)converter;
5337
}
38+
39+
internal FormDataConverter ResolveConverter(Type type)
40+
{
41+
if (!_converters.TryGetValue(type, out var converter))
42+
{
43+
throw new InvalidOperationException($"No converter registered for type '{type.FullName}'.");
44+
}
45+
46+
return converter;
47+
}
5448
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
6+
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
7+
8+
internal class NullableConverterFactory : IFormDataConverterFactory
9+
{
10+
public static bool CanConvert(Type type, FormDataSerializerOptions options)
11+
{
12+
var underlyingType = Nullable.GetUnderlyingType(type);
13+
return underlyingType != null && options.HasConverter(underlyingType);
14+
}
15+
16+
public static FormDataConverter CreateConverter(Type type, FormDataSerializerOptions options)
17+
{
18+
var underlyingType = Nullable.GetUnderlyingType(type);
19+
Debug.Assert(underlyingType != null);
20+
var underlyingConverter = options.ResolveConverter(underlyingType);
21+
Debug.Assert(underlyingConverter != null);
22+
var expectedConverterType = typeof(NullableConverter<>).MakeGenericType(underlyingType);
23+
Debug.Assert(expectedConverterType != null);
24+
return Activator.CreateInstance(expectedConverterType, underlyingConverter) as FormDataConverter ??
25+
throw new InvalidOperationException($"Unable to create converter for type '{type}'.");
26+
}
27+
}
28+
29+
internal class NullableConverter<T> : FormDataConverter<T?> where T : struct
30+
{
31+
private readonly FormDataConverter<T> _nonNullableConverter;
32+
33+
public NullableConverter(FormDataConverter<T> nonNullableConverter)
34+
{
35+
_nonNullableConverter = nonNullableConverter;
36+
}
37+
38+
internal override bool TryRead(ref FormDataReader context, Type type, FormDataSerializerOptions options, out T? result, out bool found)
39+
{
40+
if (!(_nonNullableConverter.TryRead(ref context, type, options, out var innerResult, out found) && found))
41+
{
42+
result = null;
43+
return false;
44+
}
45+
else
46+
{
47+
result = innerResult;
48+
return true;
49+
}
50+
}
51+
}

src/Components/Endpoints/src/Binding/ParsableConverter.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
55

6+
internal class ParsableConverterFactory : IFormDataConverterFactory
7+
{
8+
public static bool CanConvert(Type type, FormDataSerializerOptions options)
9+
{
10+
// returns whether type implements IParsable<T>
11+
return typeof(IParsable<>).MakeGenericType(type).IsAssignableFrom(type);
12+
}
13+
14+
public static FormDataConverter CreateConverter(Type type, FormDataSerializerOptions options)
15+
{
16+
return typeof(ParsableConverter<>)
17+
.MakeGenericType(type)
18+
.GetConstructor(Type.EmptyTypes)!
19+
.Invoke(null) as FormDataConverter ??
20+
throw new InvalidOperationException($"Unable to create converter for '{type.FullName}'.");
21+
}
22+
}
23+
624
internal class ParsableConverter<T> : FormDataConverter<T>, ISingleValueConverter where T : IParsable<T>
725
{
826
internal override bool TryRead(ref FormDataReader reader, Type type, FormDataSerializerOptions options, out T? result, out bool found)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
5+
6+
internal static class WellKnownConverters
7+
{
8+
public static readonly IReadOnlyDictionary<Type, FormDataConverter> Converters;
9+
10+
static WellKnownConverters()
11+
{
12+
var converters = new Dictionary<Type, FormDataConverter>
13+
{
14+
// For the most common types, we avoid going through the factories and just
15+
// create the converters directly. This is a performance optimization.
16+
{ typeof(string), new ParsableConverter<string>() },
17+
{ typeof(char), new ParsableConverter<char>() },
18+
{ typeof(bool), new ParsableConverter<bool>() },
19+
{ typeof(byte), new ParsableConverter<byte>() },
20+
{ typeof(sbyte), new ParsableConverter<sbyte>() },
21+
{ typeof(ushort), new ParsableConverter<ushort>() },
22+
{ typeof(uint), new ParsableConverter<uint>() },
23+
{ typeof(ulong), new ParsableConverter<ulong>() },
24+
{ typeof(Int128), new ParsableConverter<Int128>() },
25+
{ typeof(short), new ParsableConverter<short>() },
26+
{ typeof(int), new ParsableConverter<int>() },
27+
{ typeof(long), new ParsableConverter<long>() },
28+
{ typeof(UInt128), new ParsableConverter<UInt128>() },
29+
{ typeof(Half), new ParsableConverter<Half>() },
30+
{ typeof(float), new ParsableConverter<float>() },
31+
{ typeof(double), new ParsableConverter<double>() },
32+
{ typeof(decimal), new ParsableConverter<decimal>() },
33+
{ typeof(DateOnly), new ParsableConverter<DateOnly>() },
34+
{ typeof(DateTime), new ParsableConverter<DateTime>() },
35+
{ typeof(DateTimeOffset), new ParsableConverter<DateTimeOffset>() },
36+
{ typeof(TimeSpan), new ParsableConverter<TimeSpan>() },
37+
{ typeof(TimeOnly), new ParsableConverter<TimeOnly>() },
38+
{ typeof(Guid), new ParsableConverter<Guid>() }
39+
};
40+
41+
converters.Add(typeof(char?), new NullableConverter<char>((FormDataConverter<char>)converters[typeof(char)]));
42+
converters.Add(typeof(bool?), new NullableConverter<bool>((FormDataConverter<bool>)converters[typeof(bool)]));
43+
converters.Add(typeof(byte?), new NullableConverter<byte>((FormDataConverter<byte>)converters[typeof(byte)]));
44+
converters.Add(typeof(sbyte?), new NullableConverter<sbyte>((FormDataConverter<sbyte>)converters[typeof(sbyte)]));
45+
converters.Add(typeof(ushort?), new NullableConverter<ushort>((FormDataConverter<ushort>)converters[typeof(ushort)]));
46+
converters.Add(typeof(uint?), new NullableConverter<uint>((FormDataConverter<uint>)converters[typeof(uint)]));
47+
converters.Add(typeof(ulong?), new NullableConverter<ulong>((FormDataConverter<ulong>)converters[typeof(ulong)]));
48+
converters.Add(typeof(Int128?), new NullableConverter<Int128>((FormDataConverter<Int128>)converters[typeof(Int128)]));
49+
converters.Add(typeof(short?), new NullableConverter<short>((FormDataConverter<short>)converters[typeof(short)]));
50+
converters.Add(typeof(int?), new NullableConverter<int>((FormDataConverter<int>)converters[typeof(int)]));
51+
converters.Add(typeof(long?), new NullableConverter<long>((FormDataConverter<long>)converters[typeof(long)]));
52+
converters.Add(typeof(UInt128?), new NullableConverter<UInt128>((FormDataConverter<UInt128>)converters[typeof(UInt128)]));
53+
converters.Add(typeof(Half?), new NullableConverter<Half>((FormDataConverter<Half>)converters[typeof(Half)]));
54+
converters.Add(typeof(float?), new NullableConverter<float>((FormDataConverter<float>)converters[typeof(float)]));
55+
converters.Add(typeof(double?), new NullableConverter<double>((FormDataConverter<double>)converters[typeof(double)]));
56+
converters.Add(typeof(decimal?), new NullableConverter<decimal>((FormDataConverter<decimal>)converters[typeof(decimal)]));
57+
converters.Add(typeof(DateOnly?), new NullableConverter<DateOnly>((FormDataConverter<DateOnly>)converters[typeof(DateOnly)]));
58+
converters.Add(typeof(DateTime?), new NullableConverter<DateTime>((FormDataConverter<DateTime>)converters[typeof(DateTime)]));
59+
converters.Add(typeof(DateTimeOffset?), new NullableConverter<DateTimeOffset>((FormDataConverter<DateTimeOffset>)converters[typeof(DateTimeOffset)]));
60+
converters.Add(typeof(TimeSpan?), new NullableConverter<TimeSpan>((FormDataConverter<TimeSpan>)converters[typeof(TimeSpan)]));
61+
converters.Add(typeof(TimeOnly?), new NullableConverter<TimeOnly>((FormDataConverter<TimeOnly>)converters[typeof(TimeOnly)]));
62+
converters.Add(typeof(Guid?), new NullableConverter<Guid>((FormDataConverter<Guid>)converters[typeof(Guid)]));
63+
64+
Converters = converters;
65+
}
66+
}

0 commit comments

Comments
 (0)