Skip to content

Commit 8ce349b

Browse files
javiercngithub-actions
authored andcommitted
Address feedback and update E2E tests
1 parent 28e7b26 commit 8ce349b

16 files changed

+158
-74
lines changed

src/Components/Endpoints/src/Binding/Converters/DictionaryConverter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ internal override bool TryRead(
6464
if (!TKey.TryParse(key[1..^1].Span, CultureInfo.InvariantCulture, out var keyValue))
6565
{
6666
succeded = false;
67+
var currentPrefix = context.GetPrefix();
6768
context.AddMappingError(
68-
FormattableStringFactory.Create(FormDataResources.DictionaryUnparsableKey, key[1..^1], typeof(TKey).FullName),
69+
FormattableStringFactory.Create(FormDataResources.DictionaryUnparsableKey, key[1..^1], currentPrefix),
6970
null);
7071

7172
continue;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ internal override bool TryRead(ref FormDataReader reader, Type type, FormDataMap
2222
}
2323
else
2424
{
25-
reader.AddMappingError(FormattableStringFactory.Create(FormDataResources.ParsableMappingError, value, type.FullName), value);
25+
var segment = reader.GetLastPrefixSegment();
26+
reader.AddMappingError(FormattableStringFactory.Create(FormDataResources.ParsableMappingError, value, segment), value);
2627
result = default;
2728
return false;
2829
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Microsoft.AspNetCore.Components.Binding;
99
using Microsoft.AspNetCore.Components.Endpoints.Binding;
1010
using Microsoft.AspNetCore.Components.Forms;
11-
using Microsoft.AspNetCore.Http.Features;
1211
using Microsoft.Extensions.Primitives;
1312

1413
namespace Microsoft.AspNetCore.Components.Endpoints;
@@ -87,10 +86,10 @@ public override void Deserialize(
8786
}
8887
buffer = ArrayPool<char>.Shared.Rent(options.MaxKeyBufferSize);
8988

90-
// Form values are parsed according to the culture of the request, which is set to the current culture by the localization middleware.
91-
// Some form input types use the invariant culture when sending the data to the server. For those cases, we'll
92-
// provide a way to override the culture to use to parse that value.
93-
var reader = new FormDataReader(dictionary, CultureInfo.CurrentCulture, buffer.AsMemory(options.MaxKeyBufferSize))
89+
var reader = new FormDataReader(
90+
dictionary,
91+
options.UseCurrentCulture ? CultureInfo.CurrentCulture : CultureInfo.InvariantCulture,
92+
buffer.AsMemory(0, options.MaxKeyBufferSize))
9493
{
9594
ErrorHandler = context.OnError,
9695
AttachInstanceToErrorsHandler = context.MapErrorToContainer

src/Components/Endpoints/src/Binding/FormDataMapperOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections.Concurrent;
55
using System.Diagnostics.CodeAnalysis;
6-
using Microsoft.AspNetCore.Http.Features;
76
using Microsoft.AspNetCore.WebUtilities;
87

98
namespace Microsoft.AspNetCore.Components.Endpoints.Binding;
@@ -35,6 +34,8 @@ public FormDataMapperOptions()
3534

3635
internal int MaxKeyBufferSize = FormReader.DefaultKeyLengthLimit;
3736

37+
internal bool UseCurrentCulture;
38+
3839
internal bool HasConverter(Type valueType) => _converters.ContainsKey(valueType);
3940

4041
internal bool IsSingleValueConverter(Type type)

src/Components/Endpoints/src/Binding/FormDataReader.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,26 @@ internal readonly bool TryGetValue([NotNullWhen(true)] out string? value)
174174
return foundSingleValue;
175175
}
176176

177+
internal string GetPrefix() => _currentPrefixBuffer.ToString();
178+
179+
internal string GetLastPrefixSegment()
180+
{
181+
var index = _currentPrefixBuffer.Span.LastIndexOfAny(".[");
182+
if (index == -1)
183+
{
184+
return _currentPrefixBuffer.ToString();
185+
}
186+
if (_currentPrefixBuffer.Span[index] == '.')
187+
{
188+
return _currentPrefixBuffer.Span[(index + 1)..].ToString();
189+
}
190+
else
191+
{
192+
// Return the value without the closing bracket ]
193+
return _currentPrefixBuffer.Span[(index + 1)..^1].ToString();
194+
}
195+
}
196+
177197
internal readonly struct FormKeyCollection : IEnumerable<ReadOnlyMemory<char>>
178198
{
179199
private readonly HashSet<FormKey> _values;

src/Components/Endpoints/src/Binding/FormDataResources.resx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120120
<data name="DictionaryUnparsableKey" xml:space="preserve">
121-
<value>Could not convert key '{0}' to '{1}'.</value>
121+
<value>The value '{0}' is not a valid key for '{1}'.</value>
122122
</data>
123123
<data name="MaxCollectionSizeReached" xml:space="preserve">
124124
<value>The number of elements in the {0} exceeded the maximum number of '{1}' elements allowed.</value>
125125
</data>
126126
<data name="ParsableMappingError" xml:space="preserve">
127-
<value>Could not convert value '{0}' to '{1}'.</value>
127+
<value>The value '{0}' is not valid for '{1}'.</value>
128128
</data>
129129
</root>

src/Components/Endpoints/src/Binding/HttpContextFormDataProvider.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections.ObjectModel;
55
using Microsoft.AspNetCore.Components.Forms;
6-
using Microsoft.AspNetCore.Http.Features;
76
using Microsoft.Extensions.Primitives;
87

98
namespace Microsoft.AspNetCore.Components.Endpoints;

src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
using System.Text;
77
using System.Text.Encodings.Web;
88
using Microsoft.AspNetCore.Http;
9-
using Microsoft.AspNetCore.Http.Features;
109
using Microsoft.AspNetCore.WebUtilities;
1110
using Microsoft.Extensions.DependencyInjection;
1211

1312
namespace Microsoft.AspNetCore.Components.Endpoints;
1413

1514
internal class RazorComponentEndpointInvoker
1615
{
17-
private static readonly FormOptions DefaultFormOptions = new();
1816
private readonly HttpContext _context;
1917
private readonly EndpointHtmlRenderer _renderer;
2018
private readonly Type _rootComponentType;
@@ -45,14 +43,11 @@ private async Task RenderComponentCore()
4543
return;
4644
}
4745

48-
var formOptions = _context.GetEndpoint()!.Metadata.GetMetadata<FormOptions>() ?? DefaultFormOptions;
49-
5046
await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync(
5147
_context,
5248
componentType: _componentType,
5349
handler: handler,
54-
form: handler != null && _context.Request.HasFormContentType ? await _context.Request.ReadFormAsync(formOptions) : null,
55-
formOptions: formOptions);
50+
form: handler != null && _context.Request.HasFormContentType ? await _context.Request.ReadFormAsync() : null);
5651

5752
await using var writer = CreateResponseWriter(_context.Response.Body);
5853

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
using Microsoft.AspNetCore.Components.Routing;
1515
using Microsoft.AspNetCore.Http;
1616
using Microsoft.AspNetCore.Http.Extensions;
17-
using Microsoft.AspNetCore.Http.Features;
1817
using Microsoft.AspNetCore.Routing;
1918
using Microsoft.Extensions.DependencyInjection;
2019
using Microsoft.Extensions.Logging;
@@ -71,8 +70,7 @@ internal static async Task InitializeStandardComponentServicesAsync(
7170
HttpContext httpContext,
7271
Type? componentType = null,
7372
string? handler = null,
74-
IFormCollection? form = null,
75-
FormOptions? formOptions = null)
73+
IFormCollection? form = null)
7674
{
7775
var navigationManager = (IHostEnvironmentNavigationManager)httpContext.RequestServices.GetRequiredService<NavigationManager>();
7876
navigationManager?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request));

src/Components/Endpoints/test/Binding/FormDataMapperTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public void Throws_ForInvalidValues()
8282
var exception = Assert.Throws<FormDataMappingException>(() => FormDataMapper.Map<int>(reader, options));
8383
Assert.NotNull(exception?.Error);
8484
Assert.Equal("value", exception.Error.Key);
85-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", exception.Error.Message.ToString(reader.Culture));
85+
Assert.Equal("The value 'abc' is not valid for 'value'.", exception.Error.Message.ToString(reader.Culture));
8686
Assert.Equal("abc", exception.Error.Value);
8787
}
8888

@@ -107,7 +107,7 @@ public void CanCollectErrors_WithCustomHandler_ForInvalidValues()
107107
var error = Assert.Single(errors);
108108
Assert.NotNull(error);
109109
Assert.Equal("value", error.Key);
110-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", error.Message.ToString(reader.Culture));
110+
Assert.Equal("The value 'abc' is not valid for 'value'.", error.Message.ToString(reader.Culture));
111111
Assert.Equal("abc", error.Value);
112112
}
113113

@@ -387,13 +387,13 @@ public void Deserialize_Collections_ContinuesParsingAfterErrors()
387387
e =>
388388
{
389389
Assert.Equal("[0]", e.Key);
390-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", e.Message.ToString(reader.Culture));
390+
Assert.Equal("The value 'abc' is not valid for '0'.", e.Message.ToString(reader.Culture));
391391
Assert.Equal("abc", e.Value);
392392
},
393393
e =>
394394
{
395395
Assert.Equal("[4]", e.Key);
396-
Assert.Equal("Could not convert value 'def' to 'System.Int32'.", e.Message.ToString(reader.Culture));
396+
Assert.Equal("The value 'def' is not valid for '4'.", e.Message.ToString(reader.Culture));
397397
Assert.Equal("def", e.Value);
398398
});
399399
}
@@ -896,13 +896,13 @@ public void Deserialize_Dictionary_ContinuesParsingAfterErrors()
896896
e =>
897897
{
898898
Assert.Equal("[0]", e.Key);
899-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", e.Message.ToString(reader.Culture));
899+
Assert.Equal("The value 'abc' is not valid for '0'.", e.Message.ToString(reader.Culture));
900900
Assert.Equal("abc", e.Value);
901901
},
902902
e =>
903903
{
904904
Assert.Equal("[4]", e.Key);
905-
Assert.Equal("Could not convert value 'def' to 'System.Int32'.", e.Message.ToString(reader.Culture));
905+
Assert.Equal("The value 'def' is not valid for '4'.", e.Message.ToString(reader.Culture));
906906
Assert.Equal("def", e.Value);
907907
});
908908
}
@@ -955,13 +955,13 @@ public void Deserialize_SkipsElement_WhenFailsToParseKey()
955955
e =>
956956
{
957957
Assert.Equal("", e.Key);
958-
Assert.Equal("Could not convert key 'abc' to 'System.Int32'.", e.Message.ToString(reader.Culture));
958+
Assert.Equal("The value 'abc' is not a valid key for ''.", e.Message.ToString(reader.Culture));
959959
Assert.Null(e.Value);
960960
},
961961
e =>
962962
{
963963
Assert.Equal("", e.Key);
964-
Assert.Equal("Could not convert key 'def' to 'System.Int32'.", e.Message.ToString(reader.Culture));
964+
Assert.Equal("The value 'def' is not a valid key for ''.", e.Message.ToString(reader.Culture));
965965
Assert.Null(e.Value);
966966
});
967967
}
@@ -1115,7 +1115,7 @@ public void Deserialize_ComplexType_ContinuesMappingAfterPropertyError()
11151115

11161116
var error = Assert.Single(errors);
11171117
Assert.Equal("Age", error.Key);
1118-
var expectedMessage = "Could not convert value 'abc' to 'System.Int32'.";
1118+
var expectedMessage = "The value 'abc' is not valid for 'Age'.";
11191119
var actualMessage = error.Message.ToString(reader.Culture);
11201120
Assert.Equal(expectedMessage, actualMessage);
11211121
Assert.Equal("abc", error.Value);

src/Components/Forms/test/ExpressionFormatterTest.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void Works_MemberAccessOnly()
1515
var result = ExpressionFormatter.FormatLambda(() => person.Parent.Name);
1616

1717
// Assert
18-
Assert.Equal("person.Container.Name", result);
18+
Assert.Equal("person.Parent.Name", result);
1919
}
2020

2121
[Fact]
@@ -28,7 +28,7 @@ public void Works_MemberAccessWithConstIndex()
2828
var result = ExpressionFormatter.FormatLambda(() => person.Parent.Children[3].Name);
2929

3030
// Assert
31-
Assert.Equal("person.Container.Children[3].Name", result);
31+
Assert.Equal("person.Parent.Children[3].Name", result);
3232
}
3333

3434
[Fact]
@@ -45,9 +45,9 @@ public void Works_MemberAccessWithConstIndex_SameLambdaMultipleTimes()
4545
}
4646

4747
// Assert
48-
Assert.Equal("person.Container.Children[3].Name", result[0]);
49-
Assert.Equal("person.Container.Children[3].Name", result[1]);
50-
Assert.Equal("person.Container.Children[3].Name", result[2]);
48+
Assert.Equal("person.Parent.Children[3].Name", result[0]);
49+
Assert.Equal("person.Parent.Children[3].Name", result[1]);
50+
Assert.Equal("person.Parent.Children[3].Name", result[2]);
5151
}
5252

5353
[Fact]
@@ -61,7 +61,7 @@ public void Works_MemberAccessWithVariableIndex()
6161
var result = ExpressionFormatter.FormatLambda(() => person.Parent.Children[i].Name);
6262

6363
// Assert
64-
Assert.Equal("person.Container.Children[42].Name", result);
64+
Assert.Equal("person.Parent.Children[42].Name", result);
6565
}
6666

6767
[Fact]
@@ -79,9 +79,9 @@ public void Works_ForLoopIteratorVariableIndex_Short()
7979
}
8080

8181
// Assert
82-
Assert.Equal("person.Container.Children[0].Name", result[0]);
83-
Assert.Equal("person.Container.Children[1].Name", result[1]);
84-
Assert.Equal("person.Container.Children[2].Name", result[2]);
82+
Assert.Equal("person.Parent.Children[0].Name", result[0]);
83+
Assert.Equal("person.Parent.Children[1].Name", result[1]);
84+
Assert.Equal("person.Parent.Children[2].Name", result[2]);
8585
}
8686

8787
[Fact]
@@ -95,13 +95,13 @@ public void Works_ForLoopIteratorVariableIndex_MultipleClosures()
9595
var result2 = ComputeResult();
9696

9797
// Assert
98-
Assert.Equal("person.Container.Children[0].Name", result1[0]);
99-
Assert.Equal("person.Container.Children[1].Name", result1[1]);
100-
Assert.Equal("person.Container.Children[2].Name", result1[2]);
98+
Assert.Equal("person.Parent.Children[0].Name", result1[0]);
99+
Assert.Equal("person.Parent.Children[1].Name", result1[1]);
100+
Assert.Equal("person.Parent.Children[2].Name", result1[2]);
101101

102-
Assert.Equal("person.Container.Children[0].Name", result2[0]);
103-
Assert.Equal("person.Container.Children[1].Name", result2[1]);
104-
Assert.Equal("person.Container.Children[2].Name", result2[2]);
102+
Assert.Equal("person.Parent.Children[0].Name", result2[0]);
103+
Assert.Equal("person.Parent.Children[1].Name", result2[1]);
104+
Assert.Equal("person.Parent.Children[2].Name", result2[2]);
105105

106106
string[] ComputeResult()
107107
{
@@ -131,9 +131,9 @@ public void Works_ForLoopIteratorVariableIndex_Long()
131131
}
132132

133133
// Assert
134-
Assert.Equal("person.Container.Container.Children[0].Container.Children[0].Children[0].Name", result[0]);
135-
Assert.Equal("person.Container.Container.Children[1].Container.Children[1].Children[1].Name", result[1]);
136-
Assert.Equal("person.Container.Container.Children[2].Container.Children[2].Children[2].Name", result[2]);
134+
Assert.Equal("person.Parent.Parent.Children[0].Parent.Children[0].Children[0].Name", result[0]);
135+
Assert.Equal("person.Parent.Parent.Children[1].Parent.Children[1].Children[1].Name", result[1]);
136+
Assert.Equal("person.Parent.Parent.Children[2].Parent.Children[2].Children[2].Name", result[2]);
137137
}
138138

139139
[Fact]
@@ -151,9 +151,9 @@ public void Works_ForLoopIteratorVariableIndex_NonArrayType()
151151
}
152152

153153
// Assert
154-
Assert.Equal("person.Container.Nicknames[0]", result[0]);
155-
Assert.Equal("person.Container.Nicknames[1]", result[1]);
156-
Assert.Equal("person.Container.Nicknames[2]", result[2]);
154+
Assert.Equal("person.Parent.Nicknames[0]", result[0]);
155+
Assert.Equal("person.Parent.Nicknames[1]", result[1]);
156+
Assert.Equal("person.Parent.Nicknames[2]", result[2]);
157157
}
158158

159159
public void Dispose()

src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ public void CanDisplayErrorsFromMultipleParametersToTheDefaultForm(bool suppress
126126
errors,
127127
error =>
128128
{
129-
Assert.Equal("Could not convert value 'abcd' to 'System.Int32'.", error.Text);
129+
Assert.Equal("The value 'abcd' is not valid for 'Parameter'.", error.Text);
130130
},
131131
error =>
132132
{
133-
Assert.Equal("Could not convert value 'invalid' to 'System.Boolean'.", error.Text);
133+
Assert.Equal("The value 'invalid' is not valid for 'OtherParameter'.", error.Text);
134134
});
135135
Assert.Equal("abcd", Browser.FindElement(By.CssSelector("input[name=Parameter]")).GetAttribute("value"));
136136
Assert.Equal("invalid", Browser.FindElement(By.CssSelector("input[name=OtherParameter]")).GetAttribute("value"));
@@ -160,7 +160,7 @@ public void CanHandleBindingErrorsBindParameterToTheDefaultForm(bool suppressEnh
160160
errors,
161161
error =>
162162
{
163-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", error.Text);
163+
Assert.Equal("The value 'abc' is not valid for 'Parameter'.", error.Text);
164164
});
165165
Assert.Equal("abc", Browser.FindElement(By.CssSelector("input[name=Parameter]")).GetAttribute("value"));
166166
}
@@ -246,8 +246,8 @@ public void CanDisplayBindingErrorsComplexTypeToDefaultForm(bool suppressEnhance
246246

247247
Browser.Click(By.Id("send"));
248248

249-
Browser.Exists(By.CssSelector("li.validation-message")).Text.Contains("Could not convert value 'invalid' to 'System.Boolean'.");
250-
Browser.Exists(By.CssSelector("div.validation-message")).Text.Contains("Could not convert value 'invalid' to 'System.Boolean'.");
249+
Browser.Exists(By.CssSelector("li.validation-message")).Text.Contains("The value 'invalid' is not valid for 'value'.");
250+
Browser.Exists(By.CssSelector("div.validation-message")).Text.Contains("The value 'invalid' is not valid for 'value'.");
251251

252252
if (!suppressEnhancedNavigation)
253253
{
@@ -443,8 +443,8 @@ public void CanDisplayBindingErrorsCollectionsToDefaultForm(bool suppressEnhance
443443

444444
Browser.Click(By.Id("send"));
445445

446-
Browser.Exists(By.CssSelector("[data-index='0']")).Text.Contains("The value 'invalid0' is not valid for IsPreferred.");
447-
Browser.Exists(By.CssSelector("[data-index='1']")).Text.Contains("The value 'invalid1' is not valid for IsPreferred.");
446+
Browser.Exists(By.CssSelector("[data-index='0']")).Text.Contains("The value 'invalid0' is not valid for 'IsPreferred'.");
447+
Browser.Exists(By.CssSelector("[data-index='1']")).Text.Contains("The value 'invalid1' is not valid for 'IsPreferred'.");
448448

449449
Browser.Equal(2, () => Browser.FindElements(By.CssSelector("li.validation-message")).Count());
450450

@@ -476,7 +476,7 @@ public void CanHandleBindingErrorsBindParameterToNamedForm(bool suppressEnhanced
476476
errors,
477477
error =>
478478
{
479-
Assert.Equal("Could not convert value 'abc' to 'System.Int32'.", error.Text);
479+
Assert.Equal("The value 'abc' is not valid for 'Parameter'.", error.Text);
480480
});
481481
Assert.Equal("abc", Browser.FindElement(By.CssSelector("input[name=Parameter]")).GetAttribute("value"));
482482
}

0 commit comments

Comments
 (0)