From 828ec2ef35e0020fb7dbea816581c437505a7940 Mon Sep 17 00:00:00 2001 From: Kahbazi Date: Wed, 9 Jan 2019 12:08:55 +0330 Subject: [PATCH 01/14] Remove nameof for event name --- .../Core/src/Internal/LoggerExtensions.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs index 0a4c6f3a5e..3ae6db3c8d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs @@ -8,16 +8,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal { // Category: DefaultHttpsProvider private static readonly Action _locatedDevelopmentCertificate = - LoggerMessage.Define(LogLevel.Debug, new EventId(0, nameof(LocatedDevelopmentCertificate)), "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})"); + LoggerMessage.Define( + LogLevel.Debug, + new EventId(0, "LocatedDevelopmentCertificate"), + "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})"); private static readonly Action _unableToLocateDevelopmentCertificate = - LoggerMessage.Define(LogLevel.Debug, new EventId(1, nameof(UnableToLocateDevelopmentCertificate)), "Unable to locate an appropriate development https certificate."); + LoggerMessage.Define( + LogLevel.Debug, + new EventId(1, "UnableToLocateDevelopmentCertificate"), + "Unable to locate an appropriate development https certificate."); private static readonly Action _failedToLocateDevelopmentCertificateFile = - LoggerMessage.Define(LogLevel.Debug, new EventId(2, nameof(FailedToLocateDevelopmentCertificateFile)), "Failed to locate the development https certificate at '{certificatePath}'."); + LoggerMessage.Define( + LogLevel.Debug, + new EventId(2, "FailedToLocateDevelopmentCertificateFile"), + "Failed to locate the development https certificate at '{certificatePath}'."); private static readonly Action _failedToLoadDevelopmentCertificate = - LoggerMessage.Define(LogLevel.Debug, new EventId(3, nameof(FailedToLoadDevelopmentCertificate)), "Failed to load the development https certificate at '{certificatePath}'."); + LoggerMessage.Define( + LogLevel.Debug, + new EventId(3, "FailedToLoadDevelopmentCertificate"), + "Failed to load the development https certificate at '{certificatePath}'."); public static void LocatedDevelopmentCertificate(this ILogger logger, X509Certificate2 certificate) => _locatedDevelopmentCertificate(logger, certificate.Subject, certificate.Thumbprint, null); From 5814a036d93246dec28ac19e4c8b1db4dda7ba32 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 28 May 2019 10:45:59 -0700 Subject: [PATCH 02/14] Port do not throw when a status code with a codefix appears multiple times in the method body (#10235) Fixes https://github.com/aspnet/AspNetCore/issues/4480 --- .../AddResponseTypeAttributeCodeFixAction.cs | 7 +++- ...AttributeCodeFixProviderIntegrationTest.cs | 3 ++ ...pleIdenticalStatusCodesAreInError.Input.cs | 34 +++++++++++++++++ ...leIdenticalStatusCodesAreInError.Output.cs | 38 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Input.cs create mode 100644 src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Output.cs diff --git a/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs b/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs index e7601b6b7f..d138d6afb1 100644 --- a/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs +++ b/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs @@ -169,7 +169,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } var statusCode = metadata.IsDefaultResponse ? 200 : metadata.StatusCode; - statusCodes.Add(statusCode, (statusCode, metadata.ReturnType)); + if (!statusCodes.ContainsKey(statusCode)) + { + // If a status code appears multiple times in the actual metadata, pick the first one to + // appear in the codefix + statusCodes.Add(statusCode, (statusCode, metadata.ReturnType)); + } } return statusCodes.Values; diff --git a/src/Mvc/Mvc.Api.Analyzers/test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/src/Mvc/Mvc.Api.Analyzers/test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs index f0cfbff1e5..9ba677d101 100644 --- a/src/Mvc/Mvc.Api.Analyzers/test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs +++ b/src/Mvc/Mvc.Api.Analyzers/test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs @@ -51,6 +51,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task CodeFixAddsStatusCodesFromObjectInitializer() => RunTest(); + [Fact] + public Task CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError() => RunTest(); + private async Task RunTest([CallerMemberName] string testMethod = "") { // Arrange diff --git a/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Input.cs b/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Input.cs new file mode 100644 index 0000000000..19d51ee710 --- /dev/null +++ b/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Input.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError : ControllerBase + { + public List Values { get; } = + new List(); + + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + var model = Values.FirstOrDefault(m => m.Id == id); + if (model == null) + { + return NotFound(); + } + + return model; + } + } + + public class CodeFixWorksWhenMultipleIdenticalStatusCodesAreInErrorModel + { + public int Id { get; set; } + } +} diff --git a/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Output.cs b/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Output.cs new file mode 100644 index 0000000000..cd7b5dad58 --- /dev/null +++ b/src/Mvc/Mvc.Api.Analyzers/test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError.Output.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWorksWhenMultipleIdenticalStatusCodesAreInError : ControllerBase + { + public List Values { get; } = + new List(); + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + var model = Values.FirstOrDefault(m => m.Id == id); + if (model == null) + { + return NotFound(); + } + + return model; + } + } + + public class CodeFixWorksWhenMultipleIdenticalStatusCodesAreInErrorModel + { + public int Id { get; set; } + } +} From 20475aa0f7aff6f9c152ec5c04e6bd0818064101 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 3 Jun 2019 09:43:31 -0700 Subject: [PATCH 03/14] Update branding to Preview 7 (#10772) --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index d7a8bb996d..6b10f69bdf 100644 --- a/version.props +++ b/version.props @@ -3,7 +3,7 @@ 3 0 0 - 6 + 7 preview$(PreReleasePreviewNumber) Preview $(PreReleasePreviewNumber) 0.3.$(AspNetCorePatchVersion) From d001f5c519c61c02c7fe63c7c98300d64af510ab Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 3 Jun 2019 10:00:53 -0700 Subject: [PATCH 04/14] Use the defaults that existed in Kestrel before (#10740) --- .../Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs | 5 ++--- .../Kestrel/Transport.Sockets/src/SocketTransportOptions.cs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 9110b39587..4ba48aa9ad 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.IO.Pipelines; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv { @@ -28,9 +27,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// public bool NoDelay { get; set; } = true; - public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + public long? MaxReadBufferSize { get; set; } = 1024 * 1024; - public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + public long? MaxWriteBufferSize { get; set; } = 64 * 1024; internal Func> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create; diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs index 2ec6c52fd0..7bd8e6e937 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.IO.Pipelines; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { @@ -25,9 +24,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets /// public bool NoDelay { get; set; } = true; - public long? MaxReadBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + public long? MaxReadBufferSize { get; set; } = 1024 * 1024; - public long? MaxWriteBufferSize { get; set; } = PipeOptions.Default.PauseWriterThreshold; + public long? MaxWriteBufferSize { get; set; } = 64 * 1024; internal Func> MemoryPoolFactory { get; set; } = System.Buffers.MemoryPoolFactory.Create; } From 34a68d5f4860e1e5767839695e45f1eeda4c9f2d Mon Sep 17 00:00:00 2001 From: Isaac Levin <8878502+isaac2004@users.noreply.github.com> Date: Mon, 3 Jun 2019 13:18:32 -0400 Subject: [PATCH 05/14] #10333 Template Updates (#10395) #10333 Template Updates --- .../WebApi-FSharp.fsproj.in | 2 +- .../Controllers/ValuesController.cs | 60 ------------------- .../Controllers/WeatherController.cs | 44 ++++++++++++++ .../Controllers/ValuesController.fs | 34 ----------- .../Controllers/WeatherController.fs | 32 ++++++++++ .../test/WebApiTemplateTest.cs | 4 +- .../test/template-baselines.json | 10 ++-- 7 files changed, 84 insertions(+), 102 deletions(-) delete mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherController.cs delete mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/WeatherController.fs diff --git a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in index 36af1dc9f5..c6017dc6c8 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in +++ b/src/ProjectTemplates/Web.ProjectTemplates/WebApi-FSharp.fsproj.in @@ -7,7 +7,7 @@ - + diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs deleted file mode 100644 index 55a45bf67f..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -#if (!NoAuth) -using Microsoft.AspNetCore.Authorization; -#endif -using Microsoft.AspNetCore.Mvc; - -namespace Company.WebApplication1.Controllers -{ -#if (!NoAuth) - [Authorize] -#endif - [Route("api/[controller]")] - [ApiController] - public class ValuesController : ControllerBase - { - // GET api/values - [HttpGet] - public ActionResult> Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public ActionResult Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody] string value) - { -#if (OrganizationalAuth || WindowsAuth) - // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803 -#endif - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { -#if (OrganizationalAuth || WindowsAuth) - // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803 -#endif - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { -#if (OrganizationalAuth || WindowsAuth) - // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803 -#endif - } - } -} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherController.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherController.cs new file mode 100644 index 0000000000..f562613ea4 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +#if (!NoAuth) +using Microsoft.AspNetCore.Authorization; +#endif +using Microsoft.AspNetCore.Mvc; + +namespace Company.WebApplication1.Controllers +{ +#if (!NoAuth) + [Authorize] +#endif + + [Route("api/SampleData/[controller]")] + [ApiController] + public class WeatherController : ControllerBase + { + [HttpGet] + public ActionResult GetWeatherForecasts(string location, TemperatureUnit unit) + { + var rng = new Random(); + return new WeatherResult + { + Location = location, + Temperature = rng.Next(-20, 55), + TemperatureUnit = unit + }; + } + } + + public enum TemperatureUnit + { + Celsius, + Fahrenheit + } + public class WeatherResult + { + public int Temperature { get; set; } + public TemperatureUnit TemperatureUnit { get; set; } + public string Location { get; set; } + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs deleted file mode 100644 index c77d5d4b79..0000000000 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Company.WebApplication1.Controllers - -open System -open System.Collections.Generic -open System.Linq -open System.Threading.Tasks -open Microsoft.AspNetCore.Mvc - -[] -[] -type ValuesController () = - inherit ControllerBase() - - [] - member this.Get() = - let values = [|"value1"; "value2"|] - ActionResult(values) - - [] - member this.Get(id:int) = - let value = "value" - ActionResult(value) - - [] - member this.Post([] value:string) = - () - - [] - member this.Put(id:int, [] value:string ) = - () - - [] - member this.Delete(id:int) = - () diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/WeatherController.fs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/WeatherController.fs new file mode 100644 index 0000000000..29ac8c9365 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/Controllers/WeatherController.fs @@ -0,0 +1,32 @@ +namespace WebApplication1.Controllers + +open System +open System.Collections.Generic +open System.Linq +open System.Threading.Tasks +open Microsoft.AspNetCore.Mvc + +type public TemperatureUnit = + | Celsius=0 + | Fahrenheit=1 + +type WeatherResult = { + Location: string + TemperatureUnit: TemperatureUnit + Temperature: int +} + +[] +[] +type WeatherController () = + inherit ControllerBase() + + [] + member this.Get(location:string, unit: TemperatureUnit) = + let rnd = System.Random() + let result:WeatherResult = { + Location = location; + Temperature = rnd.Next(-20,55); + TemperatureUnit = unit + } + ActionResult(result) diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index 3069e84d2c..5f21ac7b2c 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -48,7 +48,7 @@ namespace Templates.Test aspNetProcess.Process.HasExited, ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/api/values"); + await aspNetProcess.AssertOk("/api/SampleData/Weather"); await aspNetProcess.AssertNotFound("/"); } @@ -59,7 +59,7 @@ namespace Templates.Test ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); - await aspNetProcess.AssertOk("/api/values"); + await aspNetProcess.AssertOk("/api/SampleData/Weather"); await aspNetProcess.AssertNotFound("/"); } } diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index 279d5a6852..a6e919620c 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -404,7 +404,7 @@ "appsettings.json", "Program.cs", "Startup.cs", - "Controllers/ValuesController.cs", + "Controllers/WeatherController.cs", "Properties/launchSettings.json" ], "AuthOption": "IndividualB2C" @@ -417,7 +417,7 @@ "appsettings.json", "Program.cs", "Startup.cs", - "Controllers/ValuesController.cs", + "Controllers/WeatherController.cs", "Properties/launchSettings.json" ], "AuthOption": "SingleOrg" @@ -430,7 +430,7 @@ "appsettings.json", "Program.cs", "Startup.cs", - "Controllers/ValuesController.cs", + "Controllers/WeatherController.cs", "Properties/launchSettings.json" ], "AuthOption": "None" @@ -443,7 +443,7 @@ "appsettings.json", "Program.cs", "Startup.cs", - "Controllers/ValuesController.cs", + "Controllers/WeatherController.cs", "Properties/launchSettings.json" ], "AuthOption": "Windows" @@ -456,7 +456,7 @@ "appsettings.json", "Program.fs", "Startup.fs", - "Controllers/ValuesController.fs", + "Controllers/WeatherController.fs", "Properties/launchSettings.json" ] } From 3bb092bdab95997134e95d02c0eeb921b02caaca Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Mon, 3 Jun 2019 11:54:20 -0700 Subject: [PATCH 06/14] Unpin EF dependencies then grab the latest (#10682) - grab the latest from Blazor, EF Core (thanx @ajcvickers) and AspNetCore-Tooling - partially reverts 58c67727dfb3 --- eng/Version.Details.xml | 170 ++++++++++++++++++++-------------------- eng/Versions.props | 24 +++--- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index bbc6fcecb6..5dc1c2282c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,287 +9,287 @@ --> - + https://github.com/aspnet/Blazor - c879c3a911b4c2d6cccd4d6ff2de86a6949cda88 + 9bc8036bf68fd159fffa56f93f8b2471bf96efd4 - + https://github.com/aspnet/AspNetCore-Tooling - fd34479f7cb75a088f5517d0a79d9499fdf44036 + 0156446a321deedadd175bdb32c8d1526be5f28d - + https://github.com/aspnet/AspNetCore-Tooling - fd34479f7cb75a088f5517d0a79d9499fdf44036 + 0156446a321deedadd175bdb32c8d1526be5f28d - + https://github.com/aspnet/AspNetCore-Tooling - fd34479f7cb75a088f5517d0a79d9499fdf44036 + 0156446a321deedadd175bdb32c8d1526be5f28d - + https://github.com/aspnet/AspNetCore-Tooling - fd34479f7cb75a088f5517d0a79d9499fdf44036 + 0156446a321deedadd175bdb32c8d1526be5f28d - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/EntityFrameworkCore - 08edd86216be4857b45b47bf0a9b29e98e525c05 + fd63251e1941070438c27577ed6b0068e33a139a - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 @@ -388,7 +388,7 @@ https://github.com/dotnet/corefx e23119d577e644d2c2a25419c88c1181681358e0 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 @@ -404,7 +404,7 @@ https://github.com/dotnet/arcade e6a5d5f970bb872451c6310ae34eda31041fb552 - + https://github.com/aspnet/Extensions bfea1edf9e2e9a5465f331517149c4f543ac2ba6 diff --git a/eng/Versions.props b/eng/Versions.props index 3c04e91895..d222619545 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -49,7 +49,7 @@ 3.0.0-preview6.19279.8 - 0.10.0-preview6.19273.9 + 0.10.0-preview7.19303.2 3.0.0-preview6.19280.1 3.0.0-preview6.19280.1 @@ -113,18 +113,18 @@ 3.0.0-preview6.19280.1 3.0.0-preview6.19280.1 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 - 3.0.0-preview6.19252.4 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 + 3.0.0-preview6.19303.2 - 3.0.0-preview6.19280.2 - 3.0.0-preview6.19280.2 - 3.0.0-preview6.19280.2 - 3.0.0-preview6.19280.2 + 3.0.0-preview6.19303.1 + 3.0.0-preview6.19303.1 + 3.0.0-preview6.19303.1 + 3.0.0-preview6.19303.1 - + https://github.com/aspnet/Blazor - ff7b7c94be74f39d99043a3f5374960d78b76813 + 9bc8036bf68fd159fffa56f93f8b2471bf96efd4 - + https://github.com/aspnet/AspNetCore-Tooling - 0156446a321deedadd175bdb32c8d1526be5f28d + 6fcd12e60b3d828988cabdebb55d30397c21d0db - + https://github.com/aspnet/AspNetCore-Tooling - 0156446a321deedadd175bdb32c8d1526be5f28d + 6fcd12e60b3d828988cabdebb55d30397c21d0db - + https://github.com/aspnet/AspNetCore-Tooling - 0156446a321deedadd175bdb32c8d1526be5f28d + 6fcd12e60b3d828988cabdebb55d30397c21d0db - + https://github.com/aspnet/AspNetCore-Tooling - 0156446a321deedadd175bdb32c8d1526be5f28d + 6fcd12e60b3d828988cabdebb55d30397c21d0db - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 - + https://github.com/aspnet/EntityFrameworkCore - 730fbc079e0faa9084325e0f9331b23fec336d21 + b9c9b57de1ba41f75427db0a51b2e897d0354656 https://github.com/aspnet/Extensions diff --git a/eng/Versions.props b/eng/Versions.props index 57537daaef..1755801069 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -49,7 +49,7 @@ 3.0.0-preview6.19279.8 - 0.10.0-preview6.19273.10 + 0.10.0-preview7.19303.2 3.0.0-preview6.19303.2 3.0.0-preview6.19303.2 @@ -113,18 +113,18 @@ 3.0.0-preview6.19303.2 3.0.0-preview6.19303.2 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 + 3.0.0-preview7.19303.12 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 - 3.0.0-preview6.19303.6 + 3.0.0-preview7.19303.10 + 3.0.0-preview7.19303.10 + 3.0.0-preview7.19303.10 + 3.0.0-preview7.19303.10 - netstandard2.0 + netstandard2.1 - - + + diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/ref/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.netstandard2.0.cs b/src/Middleware/HealthChecks.EntityFrameworkCore/ref/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.netstandard2.1.cs similarity index 100% rename from src/Middleware/HealthChecks.EntityFrameworkCore/ref/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.netstandard2.0.cs rename to src/Middleware/HealthChecks.EntityFrameworkCore/ref/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.netstandard2.1.cs diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj b/src/Middleware/HealthChecks.EntityFrameworkCore/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj index fd9542da8f..ad84dcf84e 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj @@ -1,10 +1,10 @@ - + Components for performing health checks using EntityFrameworkCore. - netstandard2.0 + netstandard2.1 $(NoWarn);CS1591 true diagnostics;healthchecks;entityframeworkcore diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj b/src/Middleware/HealthChecks.EntityFrameworkCore/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj index 757393da38..45271b91bc 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0;net461 + netcoreapp3.0 Microsoft.AspNetCore.Diagnostics.HealthChecks From 03d94e0ee6e2f22a532e0e1f802effc9c1ac28e2 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 4 Jun 2019 16:59:18 -0700 Subject: [PATCH 13/14] Mark ShutdownTestRun as Flaky (#10852) --- src/Hosting/test/FunctionalTests/ShutdownTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs index 82e707487d..4c94b289a7 100644 --- a/src/Hosting/test/FunctionalTests/ShutdownTests.cs +++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs @@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests [ConditionalFact] [OSSkipCondition(OperatingSystems.Windows)] [OSSkipCondition(OperatingSystems.MacOSX)] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2577", FlakyOn.All)] public async Task ShutdownTestRun() { await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run"); From 5af8e170bc11aa189f7be0aab777b1bdfb76a431 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 4 Jun 2019 17:31:01 -0700 Subject: [PATCH 14/14] Add support for TypeConverter (#10730) * Add support for TypeConverter Fixes: #8493 Fixes: #9632 Fixes: #9339 Fixes: #8385 Fixes: 10077 This fix adds support for type converters as well as a few other minor things we were missing from binding. The key thing about supporting conversions is that we new can support arbitrary types with `@bind`. This means you can use it with generics, which is something many users have tried. Along with type converters we get Guid and TimeSpan from the BCL. The BCL also includes converters for types we're less interested in like `short`. * Use correct NumberStyles * Fix culture * Core check --- .../targets/BuiltInBclLinkerDescriptor.xml | 4 + ...ft.AspNetCore.Components.netstandard2.0.cs | 3 +- .../EventCallbackFactoryBinderExtensions.cs | 341 ++++++++++++++---- ...ventCallbackFactoryBinderExtensionsTest.cs | 152 ++++++++ src/Components/test/E2ETest/Tests/BindTest.cs | 44 +++ .../BasicTestApp/BindCasesComponent.razor | 70 ++-- .../BasicTestApp/BindGenericComponent.razor | 9 + 7 files changed, 519 insertions(+), 104 deletions(-) create mode 100644 src/Components/test/testassets/BasicTestApp/BindGenericComponent.razor diff --git a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml b/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml index 4b442b1bb8..32533df8ca 100644 --- a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml +++ b/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml @@ -13,5 +13,9 @@ + + + + diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index 382346efce..7bdacfbc4b 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -155,6 +155,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, long existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, bool? existingValue) { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, System.DateTime? existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, decimal? existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, double? existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, int? existingValue) { throw null; } @@ -162,7 +163,7 @@ namespace Microsoft.AspNetCore.Components public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, float? existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, float existingValue) { throw null; } public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, string existingValue) { throw null; } - public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, T existingValue) where T : struct, System.Enum { throw null; } + public static Microsoft.AspNetCore.Components.EventCallback CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action setter, T existingValue) { throw null; } } public static partial class EventCallbackFactoryUIEventArgsExtensions { diff --git a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs index ba0a23dd0a..16fe53ad26 100644 --- a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs +++ b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs @@ -2,13 +2,27 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Diagnostics; using System.Globalization; +using System.Reflection; namespace Microsoft.AspNetCore.Components { /// /// Contains extension methods for two-way binding using . For internal use only. /// + // + // NOTE: for number parsing, the HTML5 spec dictates that the DOM will represent + // number values as floating point numbers using `.` as the period separator. This is NOT culture senstive. + // Put another way, the user might see `,` as their decimal separator, but the value available in events + // to JS code is always simpilar to what .NET parses with InvariantCulture. + // + // See: https://www.w3.org/TR/html5/sec-forms.html#number-state-typenumber + // See: https://www.w3.org/TR/html5/infrastructure.html#valid-floating-point-number + // + // For now we're not necessarily handling this correctly since we parse the same way for number and text. public static class EventCallbackFactoryBinderExtensions { private delegate bool BindConverter(object obj, out T value); @@ -53,7 +67,7 @@ namespace Microsoft.AspNetCore.Components return false; } - if (!int.TryParse(text, out var converted)) + if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -72,7 +86,7 @@ namespace Microsoft.AspNetCore.Components return true; } - if (!int.TryParse(text, out var converted)) + if (!int.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -94,7 +108,7 @@ namespace Microsoft.AspNetCore.Components return false; } - if (!long.TryParse(text, out var converted)) + if (!long.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -113,7 +127,7 @@ namespace Microsoft.AspNetCore.Components return true; } - if (!long.TryParse(text, out var converted)) + if (!long.TryParse(text, NumberStyles.Integer, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -135,7 +149,7 @@ namespace Microsoft.AspNetCore.Components return false; } - if (!float.TryParse(text, out var converted)) + if (!float.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -154,7 +168,7 @@ namespace Microsoft.AspNetCore.Components return true; } - if (!float.TryParse(text, out var converted)) + if (!float.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -176,7 +190,7 @@ namespace Microsoft.AspNetCore.Components return false; } - if (!double.TryParse(text, out var converted)) + if (!double.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -195,7 +209,7 @@ namespace Microsoft.AspNetCore.Components return true; } - if (!double.TryParse(text, out var converted)) + if (!double.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -217,7 +231,7 @@ namespace Microsoft.AspNetCore.Components return false; } - if (!decimal.TryParse(text, out var converted)) + if (!decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -236,7 +250,7 @@ namespace Microsoft.AspNetCore.Components return true; } - if (!decimal.TryParse(text, out var converted)) + if (!decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out var converted)) { value = default; return false; @@ -246,28 +260,83 @@ namespace Microsoft.AspNetCore.Components return true; } - private static class EnumConverter where T : struct, Enum + private static BindConverter ConvertToDateTime = ConvertToDateTimeCore; + private static BindConverter ConvertToNullableDateTime = ConvertToNullableDateTimeCore; + + private static bool ConvertToDateTimeCore(object obj, out DateTime value) { - public static readonly BindConverter Convert = ConvertCore; - - public static bool ConvertCore(object obj, out T value) + var text = (string)obj; + if (string.IsNullOrEmpty(text)) { - var text = (string)obj; - if (string.IsNullOrEmpty(text)) - { - value = default; - return true; - } + value = default; + return false; + } - if (!Enum.TryParse(text, out var converted)) - { - value = default; - return false; - } + if (!DateTime.TryParse(text, CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted)) + { + value = default; + return false; + } - value = converted; + value = converted; + return true; + } + + private static bool ConvertToNullableDateTimeCore(object obj, out DateTime? value) + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; return true; } + + if (!DateTime.TryParse(text, CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted)) + { + value = default; + return false; + } + + value = converted; + return true; + } + + private static bool ConvertToEnum(object obj, out T value) where T : struct, Enum + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; + return true; + } + + if (!Enum.TryParse(text, out var converted)) + { + value = default; + return false; + } + + value = converted; + return true; + } + + private static bool ConvertToNullableEnum(object obj, out Nullable value) where T : struct, Enum + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; + return true; + } + + if (!Enum.TryParse(text, out var converted)) + { + value = default; + return false; + } + + value = converted; + return true; } /// @@ -284,7 +353,6 @@ namespace Microsoft.AspNetCore.Components Action setter, string existingValue) { - ; return CreateBinderCore(factory, receiver, setter, ConvertToString); } @@ -489,15 +557,6 @@ namespace Microsoft.AspNetCore.Components Action setter, decimal? existingValue) { - Func converter = (obj) => - { - if (decimal.TryParse((string)obj, out var value)) - { - return value; - } - - return null; - }; return CreateBinderCore(factory, receiver, setter, ConvertToNullableDecimal); } @@ -515,28 +574,24 @@ namespace Microsoft.AspNetCore.Components Action setter, DateTime existingValue) { - // Avoiding CreateBinderCore so we can avoid an extra allocating lambda - // when a format is used. - Action callback = (e) => - { - DateTime value = default; - var converted = false; - try - { - value = ConvertDateTime(e.Value, format: null); - converted = true; - } - catch - { - } + return CreateBinderCore(factory, receiver, setter, ConvertToDateTime); + } - // See comments in CreateBinderCore - if (converted) - { - setter(value); - } - }; - return factory.Create(receiver, callback); + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime? existingValue) + { + return CreateBinderCore(factory, receiver, setter, ConvertToNullableDateTime); } /// @@ -577,6 +632,23 @@ namespace Microsoft.AspNetCore.Components } }; return factory.Create(receiver, callback); + + static DateTime ConvertDateTime(object obj, string format) + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + return default; + } + else if (format != null && DateTime.TryParseExact(text, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var value)) + { + return value; + } + else + { + return DateTime.Parse(text); + } + } } /// @@ -592,26 +664,9 @@ namespace Microsoft.AspNetCore.Components this EventCallbackFactory factory, object receiver, Action setter, - T existingValue) where T : struct, Enum + T existingValue) { - return CreateBinderCore(factory, receiver, setter, EnumConverter.Convert); - } - - private static DateTime ConvertDateTime(object obj, string format) - { - var text = (string)obj; - if (string.IsNullOrEmpty(text)) - { - return default; - } - else if (format != null && DateTime.TryParseExact(text, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var value)) - { - return value; - } - else - { - return DateTime.Parse(text); - } + return CreateBinderCore(factory, receiver, setter, BinderConverterCache.Get()); } private static EventCallback CreateBinderCore( @@ -642,5 +697,139 @@ namespace Microsoft.AspNetCore.Components }; return factory.Create(receiver, callback); } + + // We can't rely on generics + static to cache here unfortunately. That would require us to overload + // CreateBinder on T : struct AND T : class, which is not allowed. + private static class BinderConverterCache + { + private readonly static ConcurrentDictionary _cache = new ConcurrentDictionary(); + + private static MethodInfo _convertToEnum; + private static MethodInfo _convertToNullableEnum; + + public static BindConverter Get() + { + if (!_cache.TryGetValue(typeof(T), out var converter)) + { + // We need to replicate all of the primitive cases that we handle here so that they will behave the same way. + // The result will be cached. + if (typeof(T) == typeof(string)) + { + converter = ConvertToString; + } + else if (typeof(T) == typeof(bool)) + { + converter = ConvertToBool; + } + else if (typeof(T) == typeof(bool?)) + { + converter = ConvertToNullableBool; + } + else if (typeof(T) == typeof(int)) + { + converter = ConvertToInt; + } + else if (typeof(T) == typeof(int?)) + { + converter = ConvertToNullableInt; + } + else if (typeof(T) == typeof(long)) + { + converter = ConvertToLong; + } + else if (typeof(T) == typeof(long?)) + { + converter = ConvertToNullableLong; + } + else if (typeof(T) == typeof(float)) + { + converter = ConvertToFloat; + } + else if (typeof(T) == typeof(float?)) + { + converter = ConvertToNullableFloat; + } + else if (typeof(T) == typeof(double)) + { + converter = ConvertToDouble; + } + else if (typeof(T) == typeof(double?)) + { + converter = ConvertToNullableDouble; + } + else if (typeof(T) == typeof(decimal)) + { + converter = ConvertToDecimal; + } + else if (typeof(T) == typeof(decimal?)) + { + converter = ConvertToNullableDecimal; + } + else if (typeof(T) == typeof(DateTime)) + { + converter = ConvertToDateTime; + } + else if (typeof(T) == typeof(DateTime?)) + { + converter = ConvertToNullableDateTime; + } + else if (typeof(T).IsEnum) + { + // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse. + var method = _convertToEnum ??= typeof(EventCallbackFactoryBinderExtensions).GetMethod(nameof(ConvertToEnum), BindingFlags.NonPublic | BindingFlags.Static); + converter = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindConverter), target: null); + } + else if (Nullable.GetUnderlyingType(typeof(T)) is Type innerType && innerType.IsEnum) + { + // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse. + var method = _convertToNullableEnum ??= typeof(EventCallbackFactoryBinderExtensions).GetMethod(nameof(ConvertToNullableEnum), BindingFlags.NonPublic | BindingFlags.Static); + converter = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindConverter), target: null); + } + else + { + converter = MakeTypeConverterConverter(); + } + + _cache.TryAdd(typeof(T), converter); + } + + return (BindConverter)converter; + } + + private static BindConverter MakeTypeConverterConverter() + { + var typeConverter = TypeDescriptor.GetConverter(typeof(T)); + if (typeConverter == null || !typeConverter.CanConvertFrom(typeof(string))) + { + throw new InvalidOperationException( + $"The type '{typeof(T).FullName}' does not have an associated {typeof(TypeConverter).Name} that supports " + + $"conversion from a string. " + + $"Apply '{typeof(TypeConverterAttribute).Name}' to the type to register a converter."); + } + + return ConvertWithTypeConverter; + + bool ConvertWithTypeConverter(object obj, out T value) + { + var text = (string)obj; + if (string.IsNullOrEmpty(text)) + { + value = default; + return true; + } + + // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow. + var converted = typeConverter.ConvertFromString(context: null, CultureInfo.CurrentCulture, text); + if (converted == null) + { + value = default; + return false; + } + + value = (T)converted; + return true; + } + } + } } } diff --git a/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs b/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs index 6779401282..84bfc72507 100644 --- a/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs +++ b/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel; +using System.Globalization; using System.Threading.Tasks; using Xunit; @@ -344,6 +346,25 @@ namespace Microsoft.AspNetCore.Components Assert.Equal(1, component.Count); } + [Fact] + public async Task CreateBinder_NullableEnum() + { + // Arrange + var value = (AttributeTargets?)AttributeTargets.All; + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var binder = EventCallback.Factory.CreateBinder(component, setter, value); + + var expectedValue = AttributeTargets.Class; + + // Act + await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), }); + + Assert.Equal(expectedValue, value); + Assert.Equal(1, component.Count); + } + [Fact] public async Task CreateBinder_DateTime() { @@ -363,6 +384,26 @@ namespace Microsoft.AspNetCore.Components Assert.Equal(1, component.Count); } + [Fact] + public async Task CreateBinder_NullableDateTime() + { + // Arrange + var value = (DateTime?)DateTime.Now; + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var binder = EventCallback.Factory.CreateBinder(component, setter, value); + + var expectedValue = new DateTime(2018, 3, 4, 1, 2, 3); + + // Act + await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), }); + + Assert.Equal(expectedValue, value); + Assert.Equal(1, component.Count); + } + + // For now format is only supported by this specific method. [Fact] public async Task CreateBinder_DateTime_Format() { @@ -383,6 +424,80 @@ namespace Microsoft.AspNetCore.Components Assert.Equal(1, component.Count); } + // This uses a type converter + [Fact] + public async Task CreateBinder_Guid() + { + // Arrange + var value = Guid.NewGuid(); + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var binder = EventCallback.Factory.CreateBinder(component, setter, value); + + var expectedValue = Guid.NewGuid(); + + // Act + await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), }); + + Assert.Equal(expectedValue, value); + Assert.Equal(1, component.Count); + } + + // This uses a type converter + [Fact] + public async Task CreateBinder_NullableGuid() + { + // Arrange + var value = (Guid?)Guid.NewGuid(); + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var binder = EventCallback.Factory.CreateBinder(component, setter, value); + + var expectedValue = Guid.NewGuid(); + + // Act + await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), }); + + Assert.Equal(expectedValue, value); + Assert.Equal(1, component.Count); + } + + [Fact] + public async Task CreateBinder_CustomTypeConverter() + { + // Arrange + var value = new SecretMessage() { Message = "A message", }; + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var binder = EventCallback.Factory.CreateBinder(component, setter, value); + + var expectedValue = new SecretMessage() { Message = "TypeConverter may be old, but it still works!", }; + + // Act + await binder.InvokeAsync(new UIChangeEventArgs() { Value = expectedValue.ToString(), }); + + Assert.Equal(expectedValue.Message, value.Message); + Assert.Equal(1, component.Count); + } + + [Fact] + public void CreateBinder_GenericWithoutTypeConverter_Throws() + { + var value = new ClassWithoutTypeConverter(); + var component = new EventCountingComponent(); + Action setter = (_) => value = _; + + var ex = Assert.Throws(() => EventCallback.Factory.CreateBinder(component, setter, value)); + + Assert.Equal( + $"The type '{typeof(ClassWithoutTypeConverter).FullName}' does not have an associated TypeConverter that supports conversion from a string. " + + $"Apply 'TypeConverterAttribute' to the type to register a converter.", + ex.Message); + } + private class EventCountingComponent : IComponent, IHandleEvent { public int Count; @@ -403,5 +518,42 @@ namespace Microsoft.AspNetCore.Components throw new System.NotImplementedException(); } } + + private class ClassWithoutTypeConverter + { + } + + [TypeConverter(typeof(SecretMessageTypeConverter))] + private class SecretMessage + { + public string Message { get; set; } + + public override string ToString() + { + return Message; + } + } + + private class SecretMessageTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + + return false; + } + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string message) + { + return new SecretMessage() { Message = message, }; + } + + return null; + } + } } } diff --git a/src/Components/test/E2ETest/Tests/BindTest.cs b/src/Components/test/E2ETest/Tests/BindTest.cs index 51fcc9ea9d..4931dd786e 100644 --- a/src/Components/test/E2ETest/Tests/BindTest.cs +++ b/src/Components/test/E2ETest/Tests/BindTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; @@ -538,5 +539,48 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.Equal("0.011", () => boundValue.Text); Assert.Equal("0.011", mirrorValue.GetAttribute("value")); } + + [Fact] + public void CanBindTextboxGenericInt() + { + var target = Browser.FindElement(By.Id("textbox-generic-int")); + var boundValue = Browser.FindElement(By.Id("textbox-generic-int-value")); + var mirrorValue = Browser.FindElement(By.Id("textbox-generic-int-mirror")); + Assert.Equal("-42", target.GetAttribute("value")); + Assert.Equal("-42", boundValue.Text); + Assert.Equal("-42", mirrorValue.GetAttribute("value")); + + // Modify target; value is not updated because it's not convertable. + target.Clear(); + Browser.Equal("-42", () => boundValue.Text); + Assert.Equal("-42", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + target.SendKeys("42\t"); + Browser.Equal("42", () => boundValue.Text); + Assert.Equal("42", mirrorValue.GetAttribute("value")); + } + + [Fact] + public void CanBindTextboxGenericGuid() + { + var target = Browser.FindElement(By.Id("textbox-generic-guid")); + var boundValue = Browser.FindElement(By.Id("textbox-generic-guid-value")); + var mirrorValue = Browser.FindElement(By.Id("textbox-generic-guid-mirror")); + Assert.Equal("00000000-0000-0000-0000-000000000000", target.GetAttribute("value")); + Assert.Equal("00000000-0000-0000-0000-000000000000", boundValue.Text); + Assert.Equal("00000000-0000-0000-0000-000000000000", mirrorValue.GetAttribute("value")); + + // Modify target; value is not updated because it's not convertable. + target.Clear(); + Browser.Equal("00000000-0000-0000-0000-000000000000", () => boundValue.Text); + Assert.Equal("00000000-0000-0000-0000-000000000000", mirrorValue.GetAttribute("value")); + + // Modify target; verify value is updated and that textboxes linked to the same data are updated + var newValue = Guid.NewGuid().ToString(); + target.SendKeys(newValue + "\t"); + Browser.Equal(newValue, () => boundValue.Text); + Assert.Equal(newValue, mirrorValue.GetAttribute("value")); + } } } diff --git a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor index 362db2936f..a8b393660b 100644 --- a/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor @@ -90,6 +90,19 @@

+

+ Generic bind (int): + + @textboxGenericIntValue + +

+

+ Generic bind (guid): + + @textboxGenericGuidValue + +

+

Text Area

Initially blank: @@ -138,36 +151,39 @@

@code { - string textboxInitiallyBlankValue = null; - string textboxInitiallyPopulatedValue = "Hello"; +string textboxInitiallyBlankValue = null; +string textboxInitiallyPopulatedValue = "Hello"; - string textAreaInitiallyBlankValue = null; - string textAreaInitiallyPopulatedValue = "Hello"; +string textAreaInitiallyBlankValue = null; +string textAreaInitiallyPopulatedValue = "Hello"; - bool? checkboxInitiallyNullValue = null; - bool checkboxInitiallyUncheckedValue = false; - bool checkboxInitiallyCheckedValue = true; +bool? checkboxInitiallyNullValue = null; +bool checkboxInitiallyUncheckedValue = false; +bool checkboxInitiallyCheckedValue = true; - int textboxIntValue = -42; - int? textboxNullableIntValue = null; - long textboxLongValue = 3_000_000_000; - long? textboxNullableLongValue = null; - float textboxFloatValue = 3.141f; - float? textboxNullableFloatValue = null; - double textboxDoubleValue = 3.14159265359d; - double? textboxNullableDoubleValue = null; - decimal textboxDecimalValue = 0.0000000000000000000000000001M; - decimal? textboxNullableDecimalValue = null; - decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M; - decimal? textboxNullableDecimalInvalidValue = null; +int textboxIntValue = -42; +int? textboxNullableIntValue = null; +long textboxLongValue = 3_000_000_000; +long? textboxNullableLongValue = null; +float textboxFloatValue = 3.141f; +float? textboxNullableFloatValue = null; +double textboxDoubleValue = 3.14159265359d; +double? textboxNullableDoubleValue = null; +decimal textboxDecimalValue = 0.0000000000000000000000000001M; +decimal? textboxNullableDecimalValue = null; +decimal textboxDecimalInvalidValue = 0.0000000000000000000000000001M; +decimal? textboxNullableDecimalInvalidValue = null; - bool includeFourthOption = false; - enum SelectableValue { First, Second, Third, Fourth } - SelectableValue selectValue = SelectableValue.Second; +int textboxGenericIntValue = -42; +Guid textboxGenericGuidValue = Guid.Empty; - void AddAndSelectNewSelectOption() - { - includeFourthOption = true; - selectValue = SelectableValue.Fourth; - } +bool includeFourthOption = false; +enum SelectableValue { First, Second, Third, Fourth } +SelectableValue selectValue = SelectableValue.Second; + +void AddAndSelectNewSelectOption() +{ + includeFourthOption = true; + selectValue = SelectableValue.Fourth; +} } diff --git a/src/Components/test/testassets/BasicTestApp/BindGenericComponent.razor b/src/Components/test/testassets/BasicTestApp/BindGenericComponent.razor new file mode 100644 index 0000000000..54072b918e --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/BindGenericComponent.razor @@ -0,0 +1,9 @@ +@typeparam TValue + + + +@code { + [Parameter] string Id { get; set; } + [Parameter] TValue Value { get; set; } + [Parameter] EventCallback ValueChanged { get; set; } +}