Skip to content

Commit dac2413

Browse files
committed
Add Support for DateOnly & TimeOnly for SupplyParameterFromQuery
Fixes: #35525 API Proposal: #35567
1 parent d05a904 commit dac2413

File tree

8 files changed

+136
-3
lines changed

8 files changed

+136
-3
lines changed

src/Components/Components/src/NavigationManagerExtensions.cs

+62
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public static class NavigationManagerExtensions
2828
[typeof(string)] = value => Format((string)value)!,
2929
[typeof(bool)] = value => Format((bool)value),
3030
[typeof(DateTime)] = value => Format((DateTime)value),
31+
[typeof(DateOnly)] = value => Format((DateOnly)value),
32+
[typeof(TimeOnly)] = value => Format((TimeOnly)value),
3133
[typeof(decimal)] = value => Format((decimal)value),
3234
[typeof(double)] = value => Format((double)value),
3335
[typeof(float)] = value => Format((float)value),
@@ -51,6 +53,18 @@ private static string Format(DateTime value)
5153
private static string? Format(DateTime? value)
5254
=> value?.ToString(CultureInfo.InvariantCulture);
5355

56+
private static string Format(DateOnly value)
57+
=> value.ToString(CultureInfo.InvariantCulture);
58+
59+
private static string? Format(DateOnly? value)
60+
=> value?.ToString(CultureInfo.InvariantCulture);
61+
62+
private static string Format(TimeOnly value)
63+
=> value.ToString(CultureInfo.InvariantCulture);
64+
65+
private static string? Format(TimeOnly? value)
66+
=> value?.ToString(CultureInfo.InvariantCulture);
67+
5468
private static string Format(decimal value)
5569
=> value.ToString(CultureInfo.InvariantCulture);
5670

@@ -289,6 +303,54 @@ public static string GetUriWithQueryParameter(this NavigationManager navigationM
289303
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, DateTime? value)
290304
=> GetUriWithQueryParameter(navigationManager, name, Format(value));
291305

306+
/// <summary>
307+
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
308+
/// added or updated.
309+
/// </summary>
310+
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
311+
/// <param name="name">The name of the parameter to add or update.</param>
312+
/// <param name="value">The value of the parameter to add or update.</param>
313+
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, DateOnly value)
314+
=> GetUriWithQueryParameter(navigationManager, name, Format(value));
315+
316+
/// <summary>
317+
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
318+
/// added, updated, or removed.
319+
/// </summary>
320+
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
321+
/// <param name="name">The name of the parameter to add or update.</param>
322+
/// <param name="value">The value of the parameter to add or update.</param>
323+
/// <remarks>
324+
/// If <paramref name="value"/> is <c>null</c>, the parameter will be removed if it exists in the URI.
325+
/// Otherwise, it will be added or updated.
326+
/// </remarks>
327+
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, DateOnly? value)
328+
=> GetUriWithQueryParameter(navigationManager, name, Format(value));
329+
330+
/// <summary>
331+
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
332+
/// added or updated.
333+
/// </summary>
334+
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
335+
/// <param name="name">The name of the parameter to add or update.</param>
336+
/// <param name="value">The value of the parameter to add or update.</param>
337+
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, TimeOnly value)
338+
=> GetUriWithQueryParameter(navigationManager, name, Format(value));
339+
340+
/// <summary>
341+
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
342+
/// added, updated, or removed.
343+
/// </summary>
344+
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
345+
/// <param name="name">The name of the parameter to add or update.</param>
346+
/// <param name="value">The value of the parameter to add or update.</param>
347+
/// <remarks>
348+
/// If <paramref name="value"/> is <c>null</c>, the parameter will be removed if it exists in the URI.
349+
/// Otherwise, it will be added or updated.
350+
/// </remarks>
351+
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, TimeOnly? value)
352+
=> GetUriWithQueryParameter(navigationManager, name, Format(value));
353+
292354
/// <summary>
293355
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
294356
/// added or updated.

src/Components/Components/src/PublicAPI.Unshipped.txt

+4
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.Crea
9191
static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, System.Action<System.TimeOnly?>! setter, System.TimeOnly? existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.ChangeEventArgs!>
9292
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateTime value) -> string!
9393
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateTime? value) -> string!
94+
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateOnly value) -> string!
95+
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateOnly? value) -> string!
96+
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.TimeOnly value) -> string!
97+
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.TimeOnly? value) -> string!
9498
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.Guid value) -> string!
9599
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.Guid? value) -> string!
96100
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, bool value) -> string!

src/Components/Components/src/Routing/RouteConstraint.cs

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public static UrlValueConstraint Parse(string template, string segment, string c
2727
{
2828
"bool" => typeof(bool),
2929
"datetime" => typeof(DateTime),
30+
"dateonly" => typeof(DateOnly),
31+
"timeonly" => typeof(TimeOnly),
3032
"decimal" => typeof(decimal),
3133
"double" => typeof(double),
3234
"float" => typeof(float),

src/Components/Components/src/Routing/UrlValueConstraint.cs

+10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ private static bool TryParse(ReadOnlySpan<char> str, out string result)
4343
private static bool TryParse(ReadOnlySpan<char> str, out DateTime result)
4444
=> DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);
4545

46+
private static bool TryParse(ReadOnlySpan<char> str, out DateOnly result)
47+
=> DateOnly.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);
48+
49+
private static bool TryParse(ReadOnlySpan<char> str, out TimeOnly result)
50+
=> TimeOnly.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);
51+
4652
private static bool TryParse(ReadOnlySpan<char> str, out decimal result)
4753
=> decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result);
4854

@@ -65,6 +71,10 @@ private static bool TryParse(ReadOnlySpan<char> str, out long result)
6571
var x when x == typeof(bool?) => new NullableTypedUrlValueConstraint<bool>(bool.TryParse),
6672
var x when x == typeof(DateTime) => new TypedUrlValueConstraint<DateTime>(TryParse),
6773
var x when x == typeof(DateTime?) => new NullableTypedUrlValueConstraint<DateTime>(TryParse),
74+
var x when x == typeof(DateOnly) => new TypedUrlValueConstraint<DateOnly>(TryParse),
75+
var x when x == typeof(DateOnly?) => new NullableTypedUrlValueConstraint<DateOnly>(TryParse),
76+
var x when x == typeof(TimeOnly) => new TypedUrlValueConstraint<TimeOnly>(TryParse),
77+
var x when x == typeof(TimeOnly?) => new NullableTypedUrlValueConstraint<TimeOnly>(TryParse),
6878
var x when x == typeof(decimal) => new TypedUrlValueConstraint<decimal>(TryParse),
6979
var x when x == typeof(decimal?) => new NullableTypedUrlValueConstraint<decimal>(TryParse),
7080
var x when x == typeof(double) => new TypedUrlValueConstraint<double>(TryParse),

src/Components/Web.JS/dist/Release/blazor.server.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/test/E2ETest/Tests/RoutingTest.cs

+50-1
Original file line numberDiff line numberDiff line change
@@ -777,21 +777,62 @@ public void CanArriveAtQueryStringPageWithNoQuery()
777777
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
778778
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
779779
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
780+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
781+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
780782
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
781783
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
782784

783785
AssertHighlightedLinks("With query parameters (none)");
784786
}
785787

786788
[Fact]
787-
public void CanArriveAtQueryStringPageWithQuery()
789+
public void CanArriveAtQueryStringPageWithStringQuery()
788790
{
789791
SetUrlViaPushState("/WithQueryParameters/Abc?stringvalue=Hello+there");
790792

791793
var app = Browser.MountTestComponent<TestRouter>();
792794
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
793795
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
794796
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
797+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
798+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
799+
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
800+
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
801+
802+
AssertHighlightedLinks("With query parameters (none)", "With query parameters (passing string value)");
803+
}
804+
805+
[Fact]
806+
public void CanArriveAtQueryStringPageWithDateTimeQuery()
807+
{
808+
var dateTime = new DateTime(2000, 1, 2, 3, 4, 5, 6);
809+
var dateOnly = new DateOnly(2000, 1, 2);
810+
var timeOnly = new TimeOnly(3, 4, 5, 6);
811+
SetUrlViaPushState($"/WithQueryParameters/Abc?&NullableDateTimeValue={dateTime.ToString("u")}&NullableDateOnlyValue={dateOnly.ToString("u")}&NullableTimeOnlyValue={timeOnly.ToString("u")}");
812+
813+
var app = Browser.MountTestComponent<TestRouter>();
814+
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
815+
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
816+
Assert.Equal(dateTime.ToString("hh:mm:ss on yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
817+
Assert.Equal(dateOnly.ToString("yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
818+
Assert.Equal(timeonly.ToString("hh:mm:ss"), app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
819+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
820+
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
821+
822+
AssertHighlightedLinks("With query parameters (none)", "With query parameters (passing Date Time values)");
823+
}
824+
825+
[Fact]
826+
public void CanArriveAtQueryStringPageWithStringQuery()
827+
{
828+
SetUrlViaPushState("/WithQueryParameters/Abc?stringvalue=Hello+there");
829+
830+
var app = Browser.MountTestComponent<TestRouter>();
831+
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
832+
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
833+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
834+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
835+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
795836
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
796837
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
797838

@@ -809,6 +850,8 @@ public void CanNavigateToQueryStringPageWithNoQuery()
809850
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
810851
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
811852
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
853+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
854+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
812855
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
813856
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
814857

@@ -827,6 +870,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
827870
Browser.Equal("Hello Abc .", () => app.FindElement(By.Id("test-info")).Text);
828871
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
829872
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
873+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
874+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
830875
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
831876
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
832877
var instanceId = app.FindElement(By.Id("instance-id")).Text;
@@ -838,6 +883,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
838883
app.FindElement(By.LinkText("With IntValue and LongValues")).Click();
839884
Browser.Equal("123", () => app.FindElement(By.Id("value-QueryInt")).Text);
840885
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
886+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
887+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
841888
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
842889
Assert.Equal("3 values (50, 100, -20)", app.FindElement(By.Id("value-LongValues")).Text);
843890
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);
@@ -847,6 +894,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
847894
Browser.Navigate().Back();
848895
Browser.Equal("0", () => app.FindElement(By.Id("value-QueryInt")).Text);
849896
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
897+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
898+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
850899
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
851900
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
852901
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);

src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
2121
<li><NavLink href="/subdir/WithQueryParameters/Abc">With query parameters (none)</NavLink></li>
2222
<li><NavLink href="/subdir/WithQueryParameters/Abc?stringvalue=Hello+there">With query parameters (passing string value)</NavLink></li>
23+
<li><NavLink href="/subdir/WithQueryParameters/Abc?&NullableDateTimeValue=2000-01-02%2003:04:05&NullableDateOnlyValue=2000-01-02&NullableTimeOnlyValue=03:04:05">With query parameters (passing Date Time values)</NavLink></li>
2324
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
2425
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
2526
<li><NavLink href="/subdir/WithLazyAssembly" id="with-lazy-assembly">With lazy assembly</NavLink></li>

src/Components/test/testassets/BasicTestApp/RouterTest/WithQueryParameters.razor

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<strong id="test-info">Hello @FirstName @OptionalLastName.</strong>
33
<p>IntValue: <strong id="value-QueryInt">@IntValue</strong></p>
44
<p>NullableDateTimeValue: <strong id="value-NullableDateTimeValue">@NullableDateTimeValue?.ToString("hh:mm:ss on yyyy-MM-dd")</strong></p>
5+
<p>NullableDateOnlyValue: <strong id="value-NullableDateOnlyValue">@NullableDateOnlyValue?.ToString("yyyy-MM-dd")</strong></p>
6+
<p>NullableTimeOnlyValue: <strong id="value-NullableTimeOnlyValue">@NullableTimeOnlyValue?.ToString("hh:mm:ss")</strong></p>
57
<p>StringValue: <strong id="value-StringValue">@StringValue</strong></p>
68
<p>LongValues: <strong id="value-LongValues">@LongValues.Length values (@string.Join(", ", LongValues.Select(x => x.ToString()).ToArray()))</strong></p>
79

@@ -10,7 +12,6 @@
1012
<p>
1113
Links:
1214
<a href="WithQueryParameters/@FirstName?intvalue=123">With IntValue</a> |
13-
<a href="WithQueryParameters/@FirstName?intvalue=123&NullableDateTimeValue=@(new DateTime(2000, 1, 2, 3, 4, 5, 6).ToString("u"))">With NullableDateTimeValue</a> |
1415
<a href="WithQueryParameters/@FirstName?l=50&l=100&l=-20&intvalue=123">With IntValue and LongValues</a> |
1516
</p>
1617

@@ -26,6 +27,10 @@
2627

2728
[Parameter, SupplyParameterFromQuery] public DateTime? NullableDateTimeValue { get ; set; }
2829

30+
[Parameter, SupplyParameterFromQuery] public DateOnly? NullableDateOnlyValue { get ; set; }
31+
32+
[Parameter, SupplyParameterFromQuery] public TimeOnly? NullableTimeOnlyValue { get ; set; }
33+
2934
[Parameter, SupplyParameterFromQuery] public string StringValue { get ; set; }
3035

3136
[Parameter, SupplyParameterFromQuery(Name = "l")] public long[] LongValues { get ; set; }

0 commit comments

Comments
 (0)