Skip to content

Commit 8dd8359

Browse files
authored
Add Support for DateOnly & TimeOnly for SupplyParameterFromQuery (#35569)
* Add Support for `DateOnly` & `TimeOnly` for `SupplyParameterFromQuery` Fixes: #35525 API Proposal: #35567 * Update RoutingTest.cs * Update RoutingTest.cs * Fix Tests
1 parent cdf9caa commit 8dd8359

File tree

6 files changed

+116
-5
lines changed

6 files changed

+116
-5
lines changed

src/Components/Components/src/NavigationManagerExtensions.cs

Lines changed: 62 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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/UrlValueConstraint.cs

Lines changed: 10 additions & 0 deletions
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/test/E2ETest/Tests/RoutingTest.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
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;
5-
using System.Linq;
64
using System.Runtime.InteropServices;
75
using BasicTestApp;
86
using BasicTestApp.RouterTest;
@@ -12,7 +10,6 @@
1210
using Microsoft.AspNetCore.Testing;
1311
using OpenQA.Selenium;
1412
using OpenQA.Selenium.Interactions;
15-
using Xunit;
1613
using Xunit.Abstractions;
1714

1815
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
@@ -777,27 +774,51 @@ public void CanArriveAtQueryStringPageWithNoQuery()
777774
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
778775
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
779776
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
777+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
778+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
780779
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
781780
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
782781

783782
AssertHighlightedLinks("With query parameters (none)");
784783
}
785784

786785
[Fact]
787-
public void CanArriveAtQueryStringPageWithQuery()
786+
public void CanArriveAtQueryStringPageWithStringQuery()
788787
{
789788
SetUrlViaPushState("/WithQueryParameters/Abc?stringvalue=Hello+there");
790789

791790
var app = Browser.MountTestComponent<TestRouter>();
792791
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
793792
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
794793
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
794+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
795+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
795796
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
796797
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
797798

798799
AssertHighlightedLinks("With query parameters (none)", "With query parameters (passing string value)");
799800
}
800801

802+
[Fact]
803+
public void CanArriveAtQueryStringPageWithDateTimeQuery()
804+
{
805+
var dateTime = new DateTime(2000, 1, 2, 3, 4, 5, 6);
806+
var dateOnly = new DateOnly(2000, 1, 2);
807+
var timeOnly = new TimeOnly(3, 4, 5, 6);
808+
SetUrlViaPushState($"/WithQueryParameters/Abc?NullableDateTimeValue=2000-01-02%2003:04:05&NullableDateOnlyValue=2000-01-02&NullableTimeOnlyValue=03:04:05");
809+
810+
var app = Browser.MountTestComponent<TestRouter>();
811+
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
812+
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
813+
Assert.Equal(dateTime.ToString("hh:mm:ss on yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
814+
Assert.Equal(dateOnly.ToString("yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
815+
Assert.Equal(timeOnly.ToString("hh:mm:ss"), app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
816+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
817+
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
818+
819+
AssertHighlightedLinks("With query parameters (none)", "With query parameters (passing Date Time values)");
820+
}
821+
801822
[Fact]
802823
public void CanNavigateToQueryStringPageWithNoQuery()
803824
{
@@ -809,6 +830,8 @@ public void CanNavigateToQueryStringPageWithNoQuery()
809830
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
810831
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
811832
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
833+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
834+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
812835
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
813836
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
814837

@@ -827,6 +850,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
827850
Browser.Equal("Hello Abc .", () => app.FindElement(By.Id("test-info")).Text);
828851
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
829852
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);
830855
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
831856
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
832857
var instanceId = app.FindElement(By.Id("instance-id")).Text;
@@ -838,6 +863,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
838863
app.FindElement(By.LinkText("With IntValue and LongValues")).Click();
839864
Browser.Equal("123", () => app.FindElement(By.Id("value-QueryInt")).Text);
840865
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
866+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
867+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
841868
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
842869
Assert.Equal("3 values (50, 100, -20)", app.FindElement(By.Id("value-LongValues")).Text);
843870
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);
@@ -847,6 +874,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
847874
Browser.Navigate().Back();
848875
Browser.Equal("0", () => app.FindElement(By.Id("value-QueryInt")).Text);
849876
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
877+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
878+
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
850879
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
851880
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
852881
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 6 additions & 1 deletion
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)