Skip to content

Merge from 'release/6.0-rc1' into 'release/6.0' #35588

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

Merged
merged 10 commits into from
Aug 24, 2021
Merged
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
2 changes: 1 addition & 1 deletion eng/scripts/CodeCheck.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ try {
}
}
# Check for changes in Unshipped in servicing branches
if ($targetBranch -like 'release*' -and $targetBranch -notlike '*preview*' -and $file -like '*PublicAPI.Unshipped.txt') {
if ($targetBranch -like 'release*' -and $targetBranch -notlike '*preview*' -and $targetBranch -notlike '*rc*' -and $file -like '*PublicAPI.Unshipped.txt') {
$changedAPIBaselines.Add($file)
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/Components/Components.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,27 @@
"src\\Components\\test\\E2ETestMigration\\Microsoft.AspNetCore.Components.Migration.E2ETests.csproj",
"src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"src\\Components\\test\\testassets\\ComponentsApp.App\\ComponentsApp.App.csproj",
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
"src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj",
"src\\Components\\test\\testassets\\LazyTestContentPackage\\LazyTestContentPackage.csproj",
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
"src\\Components\\test\\testassets\\TestServer\\Components.TestServer.csproj",
"src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj",
"src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj",
"src\\DataProtection\\Cryptography.KeyDerivation\\src\\Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj",
"src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj",
"src\\DataProtection\\Extensions\\src\\Microsoft.AspNetCore.DataProtection.Extensions.csproj",
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
"src\\Features\\JsonPatch\\src\\Microsoft.AspNetCore.JsonPatch.csproj",
"src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj",
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
"src\\Html.Abstractions\\src\\Microsoft.AspNetCore.Html.Abstractions.csproj",
"src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj",
"src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj",
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
Expand All @@ -79,6 +83,8 @@
"src\\Identity\\Extensions.Stores\\src\\Microsoft.Extensions.Identity.Stores.csproj",
"src\\Identity\\UI\\src\\Microsoft.AspNetCore.Identity.UI.csproj",
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
"src\\Localization\\Abstractions\\src\\Microsoft.Extensions.Localization.Abstractions.csproj",
"src\\Localization\\Localization\\src\\Microsoft.Extensions.Localization.csproj",
"src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj",
"src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj",
"src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj",
Expand All @@ -96,13 +102,15 @@
"src\\Mvc\\Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj",
"src\\Mvc\\Mvc.Cors\\src\\Microsoft.AspNetCore.Mvc.Cors.csproj",
"src\\Mvc\\Mvc.DataAnnotations\\src\\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj",
"src\\Mvc\\Mvc.Formatters.Json\\src\\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj",
"src\\Mvc\\Mvc.Localization\\src\\Microsoft.AspNetCore.Mvc.Localization.csproj",
"src\\Mvc\\Mvc.NewtonsoftJson\\src\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj",
"src\\Mvc\\Mvc.RazorPages\\src\\Microsoft.AspNetCore.Mvc.RazorPages.csproj",
"src\\Mvc\\Mvc.Razor\\src\\Microsoft.AspNetCore.Mvc.Razor.csproj",
"src\\Mvc\\Mvc.TagHelpers\\src\\Microsoft.AspNetCore.Mvc.TagHelpers.csproj",
"src\\Mvc\\Mvc.ViewFeatures\\src\\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj",
"src\\Mvc\\Mvc\\src\\Microsoft.AspNetCore.Mvc.csproj",
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
"src\\Razor\\Razor.Runtime\\src\\Microsoft.AspNetCore.Razor.Runtime.csproj",
"src\\Razor\\Razor\\src\\Microsoft.AspNetCore.Razor.csproj",
"src\\Security\\Authentication\\Cookies\\src\\Microsoft.AspNetCore.Authentication.Cookies.csproj",
Expand All @@ -128,7 +136,8 @@
"src\\SignalR\\common\\SignalR.Common\\src\\Microsoft.AspNetCore.SignalR.Common.csproj",
"src\\SignalR\\server\\Core\\src\\Microsoft.AspNetCore.SignalR.Core.csproj",
"src\\SignalR\\server\\SignalR\\src\\Microsoft.AspNetCore.SignalR.csproj",
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj"
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj",
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
]
}
}
}
52 changes: 45 additions & 7 deletions src/Components/Web/src/Forms/InputExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,61 @@ internal static class InputExtensions
{
try
{
if (BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue))
// We special-case bool values because BindConverter reserves bool conversion for conditional attributes.
if (typeof(TValue) == typeof(bool))
{
if (TryConvertToBool(value, out result))
{
validationErrorMessage = null;
return true;
}
}
else if (typeof(TValue) == typeof(bool?))
{
if (TryConvertToNullableBool(value, out result))
{
validationErrorMessage = null;
return true;
}
}
else if (BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue))
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = $"The {input.DisplayName ?? input.FieldIdentifier.FieldName} field is not valid.";
return false;
}

result = default;
validationErrorMessage = $"The {input.DisplayName ?? input.FieldIdentifier.FieldName} field is not valid.";
return false;
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException($"{input.GetType()} does not support the type '{typeof(TValue)}'.", ex);
}
}

private static bool TryConvertToBool<TValue>(string? value, out TValue result)
{
if (bool.TryParse(value, out var @bool))
{
result = (TValue)(object)@bool;
return true;
}

result = default!;
return false;
}

private static bool TryConvertToNullableBool<TValue>(string? value, out TValue result)
{
if (string.IsNullOrEmpty(value))
{
result = default!;
return true;
}

return TryConvertToBool(value, out result);
}
}
}
16 changes: 16 additions & 0 deletions src/Components/Web/src/Forms/InputSelect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
=> this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);

/// <inheritdoc />
protected override string? FormatValueAsString(TValue? value)
{
// We special-case bool values because BindConverter reserves bool conversion for conditional attributes.
if (typeof(TValue) == typeof(bool))
{
return (bool)(object)value! ? "true" : "false";
}
else if (typeof(TValue) == typeof(bool?))
{
return value is not null && (bool)(object)value ? "true" : "false";
}

return base.FormatValueAsString(value);
}

private void SetCurrentValueAsStringArray(string?[]? value)
{
CurrentValue = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var result)
Expand Down
1 change: 1 addition & 0 deletions src/Components/Web/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Microsoft.AspNetCore.Components.Web.PageTitle.ChildContent.set -> void
Microsoft.AspNetCore.Components.Web.PageTitle.PageTitle() -> void
override Microsoft.AspNetCore.Components.Forms.InputDate<TValue>.OnParametersSet() -> void
abstract Microsoft.AspNetCore.Components.RenderTree.WebRenderer.AttachRootComponentToBrowser(int componentId, string! domElementSelector) -> void
override Microsoft.AspNetCore.Components.Forms.InputSelect<TValue>.FormatValueAsString(TValue? value) -> string?
override Microsoft.AspNetCore.Components.RenderTree.WebRenderer.Dispose(bool disposing) -> void
override Microsoft.AspNetCore.Components.Routing.FocusOnNavigate.OnAfterRenderAsync(bool firstRender) -> System.Threading.Tasks.Task!
override Microsoft.AspNetCore.Components.Routing.FocusOnNavigate.OnParametersSet() -> void
Expand Down
64 changes: 64 additions & 0 deletions src/Components/test/E2ETest/Tests/FormsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,34 @@ public void InputSelectInteractsWithEditContext()
Browser.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
}

[Fact]
public void InputSelectInteractsWithEditContext_BoolValues()
{
var appElement = MountTypicalValidationComponent();
var ticketClassInput = new SelectElement(appElement.FindElement(By.ClassName("select-bool-values")).FindElement(By.TagName("select")));
var select = ticketClassInput.WrappedElement;
var messagesAccessor = CreateValidationMessagesAccessor(appElement);

// Invalidates on edit
Browser.Equal("valid", () => select.GetAttribute("class"));
ticketClassInput.SelectByText("true");
Browser.Equal("modified invalid", () => select.GetAttribute("class"));
Browser.Equal(new[] { "77 + 33 = 100 is a false statement, unfortunately." }, messagesAccessor);

// Nullable conversion can fail
ticketClassInput.SelectByText("(select)");
Browser.Equal("modified invalid", () => select.GetAttribute("class"));
Browser.Equal(new[]
{
"77 + 33 = 100 is a false statement, unfortunately.",
"The IsSelectMathStatementTrue field is not valid."
}, messagesAccessor);

// Can become valid
ticketClassInput.SelectByText("false");
Browser.Equal("modified valid", () => select.GetAttribute("class"));
}

[Fact]
public void InputSelectInteractsWithEditContext_MultipleAttribute()
{
Expand Down Expand Up @@ -521,6 +549,42 @@ public void InputRadioGroupsWithNamesNestedInteractWithEditContext()
IReadOnlyCollection<IWebElement> FindColorInputs() => group.FindElements(By.Name("color"));
}

[Fact]
public void InputRadioGroupWithBoolValuesInteractsWithEditContext()
{
var appElement = MountTypicalValidationComponent();
var messagesAccessor = CreateValidationMessagesAccessor(appElement);

// Validate selected inputs
Browser.False(() => FindTrueInput().Selected);
Browser.True(() => FindFalseInput().Selected);

// Validates on edit
Browser.Equal("valid", () => FindTrueInput().GetAttribute("class"));
Browser.Equal("valid", () => FindFalseInput().GetAttribute("class"));

FindTrueInput().Click();

Browser.Equal("modified valid", () => FindTrueInput().GetAttribute("class"));
Browser.Equal("modified valid", () => FindFalseInput().GetAttribute("class"));

// Can become invalid
FindFalseInput().Click();

Browser.Equal("modified invalid", () => FindTrueInput().GetAttribute("class"));
Browser.Equal("modified invalid", () => FindFalseInput().GetAttribute("class"));
Browser.Equal(new[] { "7 * 3 = 21 is a true statement." }, messagesAccessor);

IReadOnlyCollection<IWebElement> FindInputs()
=> appElement.FindElement(By.ClassName("radio-group-bool-values")).FindElements(By.TagName("input"));

IWebElement FindTrueInput()
=> FindInputs().First(i => string.Equals("True", i.GetAttribute("value")));

IWebElement FindFalseInput()
=> FindInputs().First(i => string.Equals("False", i.GetAttribute("value")));
}

[Fact]
public void CanWireUpINotifyPropertyChangedToEditContext()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
</InputSelect>
<span>@string.Join(", ", person.HostileStrings)</span>
</p>
<p class="select-bool-values">
T/F: 77 + 33 = 100<br>
<InputSelect @bind-Value="person.IsSelectMathStatementTrue">
<option>(select)</option>
<option value="true">true</option>
<option value="false">false</option>
</InputSelect>
</p>
<p class="airline">
<InputRadioGroup @bind-Value="person.Airline">
Airline:
Expand All @@ -96,6 +104,13 @@
</InputRadioGroup>
</InputRadioGroup>
</p>
<p class="radio-group-bool-values">
T/F: 7 * 3 = 21<br>
<InputRadioGroup @bind-Value="person.IsRadioMathStatementTrue">
<InputRadio Value="true" />true<br>
<InputRadio Value="false" />false<br>
</InputRadioGroup>
</p>
<p class="socks">
Socks color: <InputText @bind-Value="person.SocksColor" />
</p>
Expand Down Expand Up @@ -188,6 +203,12 @@
[Required, EnumDataType(typeof(Country))]
public Country? Country { get; set; } = null;

[Required, Range(typeof(bool), "false", "false", ErrorMessage = "77 + 33 = 100 is a false statement, unfortunately.")]
public bool? IsSelectMathStatementTrue { get; set; } = null;

[Required, Range(typeof(bool), "true", "true", ErrorMessage = "7 * 3 = 21 is a true statement.")]
public bool IsRadioMathStatementTrue { get; set; } = false;

[Required, StringLength(10), CustomValidationClassName(Valid = "valid-socks", Invalid = "invalid-socks")]
public string SocksColor { get; set; }

Expand Down
25 changes: 25 additions & 0 deletions src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Collections.Generic;

namespace Microsoft.AspNetCore.Http.Metadata
{
/// <summary>
/// Interface for accepting request media types.
/// </summary>
public interface IAcceptsMetadata
{
/// <summary>
/// Gets a list of the allowed request content types.
/// If the incoming request does not have a <c>Content-Type</c> with one of these values, the request will be rejected with a 415 response.
/// </summary>
IReadOnlyList<string> ContentTypes { get; }

/// <summary>
/// Gets the type being read from the request.
/// </summary>
Type? RequestType { get; }
}
}
7 changes: 7 additions & 0 deletions src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
*REMOVED*abstract Microsoft.AspNetCore.Http.HttpRequest.ContentType.get -> string!
Microsoft.AspNetCore.Http.IResult
Microsoft.AspNetCore.Http.IResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Http.Metadata.IAcceptsMetadata
Microsoft.AspNetCore.Http.Metadata.IAcceptsMetadata.ContentTypes.get -> System.Collections.Generic.IReadOnlyList<string!>!
Microsoft.AspNetCore.Http.Metadata.IAcceptsMetadata.RequestType.get -> System.Type?
Microsoft.AspNetCore.Http.Metadata.IFromBodyMetadata
Microsoft.AspNetCore.Http.Metadata.IFromBodyMetadata.AllowEmpty.get -> bool
Microsoft.AspNetCore.Http.Metadata.IFromHeaderMetadata
Expand All @@ -18,6 +21,10 @@ Microsoft.AspNetCore.Http.Metadata.IFromRouteMetadata.Name.get -> string?
Microsoft.AspNetCore.Http.Metadata.IFromServiceMetadata
Microsoft.AspNetCore.Http.Endpoint.Endpoint(Microsoft.AspNetCore.Http.RequestDelegate? requestDelegate, Microsoft.AspNetCore.Http.EndpointMetadataCollection? metadata, string? displayName) -> void
Microsoft.AspNetCore.Http.Endpoint.RequestDelegate.get -> Microsoft.AspNetCore.Http.RequestDelegate?
Microsoft.AspNetCore.Http.RequestDelegateResult
Microsoft.AspNetCore.Http.RequestDelegateResult.EndpointMetadata.get -> System.Collections.Generic.IReadOnlyList<object!>!
Microsoft.AspNetCore.Http.RequestDelegateResult.RequestDelegate.get -> Microsoft.AspNetCore.Http.RequestDelegate!
Microsoft.AspNetCore.Http.RequestDelegateResult.RequestDelegateResult(Microsoft.AspNetCore.Http.RequestDelegate! requestDelegate, System.Collections.Generic.IReadOnlyList<object!>! metadata) -> void
Microsoft.AspNetCore.Routing.RouteValueDictionary.TryAdd(string! key, object? value) -> bool
static readonly Microsoft.AspNetCore.Http.HttpProtocol.Http09 -> string!
static Microsoft.AspNetCore.Http.HttpProtocol.IsHttp09(string! protocol) -> bool
Expand Down
33 changes: 33 additions & 0 deletions src/Http/Http.Abstractions/src/RequestDelegateResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Http
{
/// <summary>
/// The result of creating a <see cref="RequestDelegate" /> from a <see cref="Delegate" />
/// </summary>
public sealed class RequestDelegateResult
{
/// <summary>
/// Creates a new instance of <see cref="RequestDelegateResult"/>.
/// </summary>
public RequestDelegateResult(RequestDelegate requestDelegate, IReadOnlyList<object> metadata)
{
RequestDelegate = requestDelegate;
EndpointMetadata = metadata;
}

/// <summary>
/// Gets the <see cref="RequestDelegate" />
/// </summary>
public RequestDelegate RequestDelegate { get;}

/// <summary>
/// Gets endpoint metadata inferred from creating the <see cref="RequestDelegate" />
/// </summary>
public IReadOnlyList<object> EndpointMetadata { get;}
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>ASP.NET Core common extension methods for HTTP abstractions, HTTP headers, HTTP request/response, and session state.</Description>
Expand All @@ -16,6 +16,7 @@
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ProblemDetailsJsonConverter.cs" LinkBase="Shared"/>
<Compile Include="$(SharedSourceRoot)HttpValidationProblemDetailsJsonConverter.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading