Skip to content

Commit 7bf578d

Browse files
Tyme-Bleyaertvnbaaijdvoituron
authored
[DatePicker] Prevent silently ignoring date formatting failures (#4496)
* Prevent silently ignoring date formatting failures * Add extra test, refactor ParsingErrorMessage property for all fields. * Attempt to fix culture comparison on the pipeline for failing tests. * Remove using that somehow got introduced. * Run test against protected method instead of extension method *Facepalm*. * Fix copy paste issue * Move parsing away from input base. * Revert "Move parsing away from input base." This reverts commit ec6cb1e. * Use interfaces instead to use for parsable components. --------- Co-authored-by: Vincent Baaij <vnbaaij@outlook.com> Co-authored-by: Denis Voituron <dvoituron@outlook.com>
1 parent 91381a1 commit 7bf578d

13 files changed

Lines changed: 154 additions & 30 deletions

examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,12 @@
669669
Gets the <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.FieldIdentifier"/> for the bound value.
670670
</summary>
671671
</member>
672+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.FieldDisplayName">
673+
<summary>
674+
Gets the display name of the field, using the specified display name if set; otherwise, uses the field
675+
identifier's name if the field is bound.
676+
</summary>
677+
</member>
672678
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.CurrentValue">
673679
<summary>
674680
Gets or sets the current value of the input.
@@ -767,6 +773,16 @@
767773
<param name="e"></param>
768774
<returns></returns>
769775
</member>
776+
<member name="T:Microsoft.FluentUI.AspNetCore.Components.ICultureSensitiveComponent">
777+
<summary>
778+
Defines an interface for components with values that are sensitive to culture settings, eg : parsing to string.
779+
</summary>
780+
</member>
781+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.ICultureSensitiveComponent.Culture">
782+
<summary>
783+
Gets or sets the culture of the component.
784+
</summary>
785+
</member>
770786
<member name="M:Microsoft.FluentUI.AspNetCore.Components.InputHelpers`1.GetMaxValue">
771787
<summary>
772788
Because of the limitation of the web component, the maximum value is set to 9999999999 for really large numbers.
@@ -779,6 +795,11 @@
779795
</summary>
780796
<returns>The minimum value for the underlying type</returns>
781797
</member>
798+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.IStringParsableComponent.ParsingErrorMessage">
799+
<summary>
800+
Gets or sets the error message to show when the field can not be parsed.
801+
</summary>
802+
</member>
782803
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentBodyContent.ChildContent">
783804
<summary>
784805
Gets or sets the content to be rendered inside the component.
@@ -2999,6 +3020,9 @@
29993020
By default <see cref="P:System.Globalization.CultureInfo.CurrentCulture"/> to display using the OS culture.
30003021
</summary>
30013022
</member>
3023+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase.ParsingErrorMessage">
3024+
<inheritdoc/>
3025+
</member>
30023026
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentCalendarBase.DisabledDateFunc">
30033027
<summary>
30043028
Function to know if a specific day must be disabled.
@@ -6703,6 +6727,9 @@
67036727
⚠️ Only available when Multiple = true.
67046728
</summary>
67056729
</member>
6730+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.ParsingErrorMessage">
6731+
<inheritdoc/>
6732+
</member>
67066733
<member name="M:Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.#ctor">
67076734
<summary />
67086735
</member>
@@ -8127,11 +8154,6 @@
81278154
An Id value must be set to use this property.
81288155
</summary>
81298156
</member>
8130-
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentNumberField`1.ParsingErrorMessage">
8131-
<summary>
8132-
Gets or sets the error message to show when the field can not be parsed.
8133-
</summary>
8134-
</member>
81358157
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentNumberField`1.ChildContent">
81368158
<summary>
81378159
Gets or sets the content to be rendered inside the component.
@@ -8143,6 +8165,9 @@
81438165
unless an explicit value for Min or Max is provided.
81448166
</summary>
81458167
</member>
8168+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentNumberField`1.ParsingErrorMessage">
8169+
<inheritdoc/>
8170+
</member>
81468171
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentNumberField`1.FormatValueAsString(`0)">
81478172
<summary>
81488173
Formats the value as a string. Derived classes can override this to determine the formatting used for <c>CurrentValueAsString</c>.
@@ -9036,6 +9061,9 @@
90369061
Gets or sets the child content to be rendering inside the <see cref="T:Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1"/>.
90379062
</summary>
90389063
</member>
9064+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1.ParsingErrorMessage">
9065+
<inheritdoc/>
9066+
</member>
90399067
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentRadioGroup`1.OnParametersSet">
90409068
<inheritdoc />
90419069
</member>
@@ -9091,6 +9119,9 @@
90919119
Fires when hovered value changes. Value will be null if no rating item is hovered.
90929120
</summary>
90939121
</member>
9122+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.ParsingErrorMessage">
9123+
<inheritdoc/>
9124+
</member>
90949125
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentRating.GroupName">
90959126
<summary />
90969127
</member>

src/Core/Components/Base/FluentInputBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ public abstract partial class FluentInputBase<TValue> : FluentComponentBase, IDi
145145

146146
internal virtual bool FieldBound => Field is not null || ValueExpression is not null || ValueChanged.HasDelegate;
147147

148+
/// <summary>
149+
/// Gets the display name of the field, using the specified display name if set; otherwise, uses the field
150+
/// identifier's name if the field is bound.
151+
/// </summary>
152+
internal string FieldDisplayName => DisplayName ?? (FieldBound ? FieldIdentifier.FieldName : UnknownBoundField);
153+
148154
protected async Task SetCurrentValueAsync(TValue? value)
149155
{
150156
var hasChanged = !EqualityComparer<TValue>.Default.Equals(value, Value);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// ------------------------------------------------------------------------
2+
// This file is licensed to you under the MIT License.
3+
// ------------------------------------------------------------------------
4+
5+
using System.Globalization;
6+
7+
namespace Microsoft.FluentUI.AspNetCore.Components;
8+
9+
/// <summary>
10+
/// Defines an interface for components with values that are sensitive to culture settings, eg : parsing to string.
11+
/// </summary>
12+
public interface ICultureSensitiveComponent
13+
{
14+
/// <summary>
15+
/// Gets or sets the culture of the component.
16+
/// </summary>
17+
public CultureInfo Culture { get; set; }
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ------------------------------------------------------------------------
2+
// This file is licensed to you under the MIT License.
3+
// ------------------------------------------------------------------------
4+
5+
namespace Microsoft.FluentUI.AspNetCore.Components;
6+
7+
/// <summary>
8+
/// Defines an interface for components with values that can be parsed from a string.
9+
/// </summary>
10+
public interface IStringParsableComponent
11+
{
12+
/// <summary>
13+
/// Gets or sets the error message to show when the field can not be parsed.
14+
/// </summary>
15+
public string ParsingErrorMessage { get; set; }
16+
}

src/Core/Components/DateTime/FluentCalendar.razor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ private async Task PickerYearSelectAsync(DateTime? year)
293293
/// <summary />
294294
protected override bool TryParseValueFromString(string? value, out DateTime? result, [NotNullWhen(false)] out string? validationErrorMessage)
295295
{
296-
BindConverter.TryConvertTo(value, Culture, out result);
297-
validationErrorMessage = null;
298-
return true;
296+
bool success = BindConverter.TryConvertTo(value, Culture, out result);
297+
validationErrorMessage = success ? null : string.Format(ParsingErrorMessage, FieldDisplayName);
298+
return success;
299299
}
300300

301301
/// <summary />

src/Core/Components/DateTime/FluentCalendarBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Microsoft.FluentUI.AspNetCore.Components;
99

10-
public abstract class FluentCalendarBase : FluentInputBase<DateTime?>
10+
public abstract class FluentCalendarBase : FluentInputBase<DateTime?> , ICultureSensitiveComponent, IStringParsableComponent
1111
{
1212
/// <summary>
1313
/// Gets or sets the culture of the component.
@@ -16,6 +16,10 @@ public abstract class FluentCalendarBase : FluentInputBase<DateTime?>
1616
[Parameter]
1717
public virtual CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
1818

19+
/// <inheritdoc/>
20+
[Parameter]
21+
public virtual string ParsingErrorMessage { get; set; } = "The {0} field must have a valid format.";
22+
1923
/// <summary>
2024
/// Function to know if a specific day must be disabled.
2125
/// </summary>

src/Core/Components/DateTime/FluentDatePicker.razor.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,9 @@ protected override bool TryParseValueFromString(string? value, out DateTime? res
145145
value = new DateTime(year, 1, 1).ToString(Culture.DateTimeFormat.ShortDatePattern);
146146
}
147147

148-
BindConverter.TryConvertTo(value, Culture, out result);
149-
150-
validationErrorMessage = null;
151-
return true;
148+
bool success = BindConverter.TryConvertTo(value, Culture, out result);
149+
validationErrorMessage = success ? null : string.Format(ParsingErrorMessage, FieldDisplayName);
150+
return success;
152151
}
153152

154153
private string PlaceholderAccordingToView()

src/Core/Components/List/ListComponentBase.razor.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
1717
/// Component that provides a list of options.
1818
/// </summary>
1919
/// <typeparam name="TOption"></typeparam>
20-
public abstract partial class ListComponentBase<TOption> : FluentInputBase<string?>, IAsyncDisposable where TOption : notnull
20+
public abstract partial class ListComponentBase<TOption> : FluentInputBase<string?>, IAsyncDisposable, IStringParsableComponent where TOption : notnull
2121
{
2222
private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/List/ListComponentBase.razor.js";
2323

@@ -212,6 +212,10 @@ protected string? InternalValue
212212
[Parameter]
213213
public Expression<Func<IEnumerable<TOption>>>? SelectedOptionsExpression { get; set; }
214214

215+
/// <inheritdoc/>
216+
[Parameter]
217+
public string ParsingErrorMessage { get; set; } = "The {0} field must be a (valid) number.";
218+
215219
/// <summary />
216220
protected ListComponentBase()
217221
{

src/Core/Components/NumberField/FluentNumberField.razor.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace Microsoft.FluentUI.AspNetCore.Components;
1212

13-
public partial class FluentNumberField<TValue> : FluentInputBase<TValue>, IAsyncDisposable
13+
public partial class FluentNumberField<TValue> : FluentInputBase<TValue>, IAsyncDisposable, IStringParsableComponent
1414
{
1515
private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/TextField/FluentTextField.razor.js";
1616

@@ -86,12 +86,6 @@ public partial class FluentNumberField<TValue> : FluentInputBase<TValue>, IAsync
8686
[Parameter]
8787
public string? AutoComplete { get; set; }
8888

89-
/// <summary>
90-
/// Gets or sets the error message to show when the field can not be parsed.
91-
/// </summary>
92-
[Parameter]
93-
public string ParsingErrorMessage { get; set; } = "The {0} field must be a (valid) number.";
94-
9589
/// <summary>
9690
/// Gets or sets the content to be rendered inside the component.
9791
/// </summary>
@@ -105,6 +99,10 @@ public partial class FluentNumberField<TValue> : FluentInputBase<TValue>, IAsync
10599
[Parameter]
106100
public bool UseTypeConstraints { get; set; }
107101

102+
/// <inheritdoc/>
103+
[Parameter]
104+
public string ParsingErrorMessage { get; set; } = "The {0} field must be a (valid) number.";
105+
108106
private static readonly string _stepAttributeValue = GetStepAttributeValue();
109107

110108
// If type constraints is true and min is null, set min to the minimum value of TValue.

src/Core/Components/Radio/FluentRadioGroup.razor.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
1515
/// </summary>
1616

1717
[CascadingTypeParameter(nameof(TValue))]
18-
public partial class FluentRadioGroup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TValue> : FluentInputBase<TValue>
18+
public partial class FluentRadioGroup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TValue> : FluentInputBase<TValue>, IStringParsableComponent
1919
{
2020
private readonly string _defaultGroupName = Identifier.NewId();
2121
private FluentRadioContext? _context;
@@ -40,6 +40,10 @@ public FluentRadioGroup()
4040
[CascadingParameter]
4141
private FluentRadioContext? CascadedContext { get; set; }
4242

43+
/// <inheritdoc/>
44+
[Parameter]
45+
public string ParsingErrorMessage { get; set; } = "The {0} field must be a (valid) number.";
46+
4347
/// <inheritdoc />
4448
protected override void OnParametersSet()
4549
{

0 commit comments

Comments
 (0)