Skip to content

[release/6.0-rc1] Add Support for DateOnly & TimeOnly for SupplyParameterFromQuery #35621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/Components/Components/src/NavigationManagerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public static class NavigationManagerExtensions
[typeof(string)] = value => Format((string)value)!,
[typeof(bool)] = value => Format((bool)value),
[typeof(DateTime)] = value => Format((DateTime)value),
[typeof(DateOnly)] = value => Format((DateOnly)value),
[typeof(TimeOnly)] = value => Format((TimeOnly)value),
[typeof(decimal)] = value => Format((decimal)value),
[typeof(double)] = value => Format((double)value),
[typeof(float)] = value => Format((float)value),
Expand All @@ -51,6 +53,18 @@ private static string Format(DateTime value)
private static string? Format(DateTime? value)
=> value?.ToString(CultureInfo.InvariantCulture);

private static string Format(DateOnly value)
=> value.ToString(CultureInfo.InvariantCulture);

private static string? Format(DateOnly? value)
=> value?.ToString(CultureInfo.InvariantCulture);

private static string Format(TimeOnly value)
=> value.ToString(CultureInfo.InvariantCulture);

private static string? Format(TimeOnly? value)
=> value?.ToString(CultureInfo.InvariantCulture);

private static string Format(decimal value)
=> value.ToString(CultureInfo.InvariantCulture);

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

/// <summary>
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
/// added or updated.
/// </summary>
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
/// <param name="name">The name of the parameter to add or update.</param>
/// <param name="value">The value of the parameter to add or update.</param>
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, DateOnly value)
=> GetUriWithQueryParameter(navigationManager, name, Format(value));

/// <summary>
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
/// added, updated, or removed.
/// </summary>
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
/// <param name="name">The name of the parameter to add or update.</param>
/// <param name="value">The value of the parameter to add or update.</param>
/// <remarks>
/// If <paramref name="value"/> is <c>null</c>, the parameter will be removed if it exists in the URI.
/// Otherwise, it will be added or updated.
/// </remarks>
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, DateOnly? value)
=> GetUriWithQueryParameter(navigationManager, name, Format(value));

/// <summary>
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
/// added or updated.
/// </summary>
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
/// <param name="name">The name of the parameter to add or update.</param>
/// <param name="value">The value of the parameter to add or update.</param>
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, TimeOnly value)
=> GetUriWithQueryParameter(navigationManager, name, Format(value));

/// <summary>
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
/// added, updated, or removed.
/// </summary>
/// <param name="navigationManager">The <see cref="NavigationManager"/>.</param>
/// <param name="name">The name of the parameter to add or update.</param>
/// <param name="value">The value of the parameter to add or update.</param>
/// <remarks>
/// If <paramref name="value"/> is <c>null</c>, the parameter will be removed if it exists in the URI.
/// Otherwise, it will be added or updated.
/// </remarks>
public static string GetUriWithQueryParameter(this NavigationManager navigationManager, string name, TimeOnly? value)
=> GetUriWithQueryParameter(navigationManager, name, Format(value));

/// <summary>
/// Returns a URI that is constructed by updating <see cref="NavigationManager.Uri"/> with a single parameter
/// added or updated.
Expand Down
4 changes: 4 additions & 0 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.Crea
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!>
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateTime value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateTime? value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateOnly value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.DateOnly? value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.TimeOnly value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.TimeOnly? value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.Guid value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, System.Guid? value) -> string!
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! name, bool value) -> string!
Expand Down
10 changes: 10 additions & 0 deletions src/Components/Components/src/Routing/UrlValueConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ private static bool TryParse(ReadOnlySpan<char> str, out string result)
private static bool TryParse(ReadOnlySpan<char> str, out DateTime result)
=> DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);

private static bool TryParse(ReadOnlySpan<char> str, out DateOnly result)
=> DateOnly.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);

private static bool TryParse(ReadOnlySpan<char> str, out TimeOnly result)
=> TimeOnly.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result);

private static bool TryParse(ReadOnlySpan<char> str, out decimal result)
=> decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result);

Expand All @@ -65,6 +71,10 @@ private static bool TryParse(ReadOnlySpan<char> str, out long result)
var x when x == typeof(bool?) => new NullableTypedUrlValueConstraint<bool>(bool.TryParse),
var x when x == typeof(DateTime) => new TypedUrlValueConstraint<DateTime>(TryParse),
var x when x == typeof(DateTime?) => new NullableTypedUrlValueConstraint<DateTime>(TryParse),
var x when x == typeof(DateOnly) => new TypedUrlValueConstraint<DateOnly>(TryParse),
var x when x == typeof(DateOnly?) => new NullableTypedUrlValueConstraint<DateOnly>(TryParse),
var x when x == typeof(TimeOnly) => new TypedUrlValueConstraint<TimeOnly>(TryParse),
var x when x == typeof(TimeOnly?) => new NullableTypedUrlValueConstraint<TimeOnly>(TryParse),
var x when x == typeof(decimal) => new TypedUrlValueConstraint<decimal>(TryParse),
var x when x == typeof(decimal?) => new NullableTypedUrlValueConstraint<decimal>(TryParse),
var x when x == typeof(double) => new TypedUrlValueConstraint<double>(TryParse),
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

37 changes: 33 additions & 4 deletions src/Components/test/E2ETest/Tests/RoutingTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq;
using System.Runtime.InteropServices;
using BasicTestApp;
using BasicTestApp.RouterTest;
Expand All @@ -12,7 +10,6 @@
using Microsoft.AspNetCore.Testing;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using Xunit;
using Xunit.Abstractions;

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

AssertHighlightedLinks("With query parameters (none)");
}

[Fact]
public void CanArriveAtQueryStringPageWithQuery()
public void CanArriveAtQueryStringPageWithStringQuery()
{
SetUrlViaPushState("/WithQueryParameters/Abc?stringvalue=Hello+there");

var app = Browser.MountTestComponent<TestRouter>();
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);

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

[Fact]
public void CanArriveAtQueryStringPageWithDateTimeQuery()
{
var dateTime = new DateTime(2000, 1, 2, 3, 4, 5, 6);
var dateOnly = new DateOnly(2000, 1, 2);
var timeOnly = new TimeOnly(3, 4, 5, 6);
SetUrlViaPushState($"/WithQueryParameters/Abc?NullableDateTimeValue=2000-01-02%2003:04:05&NullableDateOnlyValue=2000-01-02&NullableTimeOnlyValue=03:04:05");

var app = Browser.MountTestComponent<TestRouter>();
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(dateTime.ToString("hh:mm:ss on yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(dateOnly.ToString("yyyy-MM-dd"), app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(timeOnly.ToString("hh:mm:ss"), app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);

AssertHighlightedLinks("With query parameters (none)", "With query parameters (passing Date Time values)");
}

[Fact]
public void CanNavigateToQueryStringPageWithNoQuery()
{
Expand All @@ -809,6 +830,8 @@ public void CanNavigateToQueryStringPageWithNoQuery()
Assert.Equal("Hello Abc .", app.FindElement(By.Id("test-info")).Text);
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);

Expand All @@ -827,6 +850,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
Browser.Equal("Hello Abc .", () => app.FindElement(By.Id("test-info")).Text);
Assert.Equal("0", app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
var instanceId = app.FindElement(By.Id("instance-id")).Text;
Expand All @@ -838,6 +863,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
app.FindElement(By.LinkText("With IntValue and LongValues")).Click();
Browser.Equal("123", () => app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("3 values (50, 100, -20)", app.FindElement(By.Id("value-LongValues")).Text);
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);
Expand All @@ -847,6 +874,8 @@ public void CanNavigateBetweenPagesWithQueryStrings()
Browser.Navigate().Back();
Browser.Equal("0", () => app.FindElement(By.Id("value-QueryInt")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateTimeValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableDateOnlyValue")).Text);
Assert.Equal(string.Empty, app.FindElement(By.Id("value-NullableTimeOnlyValue")).Text);
Assert.Equal("Hello there", app.FindElement(By.Id("value-StringValue")).Text);
Assert.Equal("0 values ()", app.FindElement(By.Id("value-LongValues")).Text);
Assert.Equal(instanceId, app.FindElement(By.Id("instance-id")).Text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
<li><NavLink href="/subdir/WithQueryParameters/Abc">With query parameters (none)</NavLink></li>
<li><NavLink href="/subdir/WithQueryParameters/Abc?stringvalue=Hello+there">With query parameters (passing string value)</NavLink></li>
<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>
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
<li><NavLink href="/subdir/WithLazyAssembly" id="with-lazy-assembly">With lazy assembly</NavLink></li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<strong id="test-info">Hello @FirstName @OptionalLastName.</strong>
<p>IntValue: <strong id="value-QueryInt">@IntValue</strong></p>
<p>NullableDateTimeValue: <strong id="value-NullableDateTimeValue">@NullableDateTimeValue?.ToString("hh:mm:ss on yyyy-MM-dd")</strong></p>
<p>NullableDateOnlyValue: <strong id="value-NullableDateOnlyValue">@NullableDateOnlyValue?.ToString("yyyy-MM-dd")</strong></p>
<p>NullableTimeOnlyValue: <strong id="value-NullableTimeOnlyValue">@NullableTimeOnlyValue?.ToString("hh:mm:ss")</strong></p>
<p>StringValue: <strong id="value-StringValue">@StringValue</strong></p>
<p>LongValues: <strong id="value-LongValues">@LongValues.Length values (@string.Join(", ", LongValues.Select(x => x.ToString()).ToArray()))</strong></p>

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

Expand All @@ -26,6 +27,10 @@

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

[Parameter, SupplyParameterFromQuery] public DateOnly? NullableDateOnlyValue { get ; set; }

[Parameter, SupplyParameterFromQuery] public TimeOnly? NullableTimeOnlyValue { get ; set; }

[Parameter, SupplyParameterFromQuery] public string StringValue { get ; set; }

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