diff --git a/.azure/pipelines/stress.yml b/.azure/pipelines/stress.yml
new file mode 100644
index 0000000000..93346d96f2
--- /dev/null
+++ b/.azure/pipelines/stress.yml
@@ -0,0 +1,35 @@
+# This configuration builds the repository and runs stress
+pr: none
+
+# Don't run CI for this config
+trigger: none
+
+variables:
+- name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE
+ value: true
+- name: _TeamName
+ value: AspNetCore
+- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+ - name: _BuildArgs
+ value: /p:TeamName=$(_TeamName)
+ /p:OfficialBuildId=$(Build.BuildNumber)
+- ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}:
+ - name: _BuildArgs
+ value: ''
+jobs:
+- template: jobs/default-build.yml
+ parameters:
+ condition: ne(variables['SkipTests'], 'true')
+ jobName: Windows_Stress_Test
+ jobDisplayName: "Windows Stress test"
+ agentOs: Windows
+ isTestingJob: true
+ steps:
+ - script: .\src\Servers\Kestrel\stress\build.cmd -ci -c Release
+ displayName: Build Repo
+ - script: .\.dotnet\dotnet.exe run --project .\src\Servers\Kestrel\stress\HttpStress.csproj -c Release -aspnetlog
+ displayName: Run stress
+ artifacts:
+ - name: Windows_Test_Stress_Logs
+ path: artifacts/log/
+ publishOnError: true
diff --git a/Directory.Build.props b/Directory.Build.props
index d7c5acd6b0..06e288aa81 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -59,11 +59,9 @@
$(MSBuildThisFileDirectory)src\Shared\
$(RepoRoot)src\submodules\googletest\
+
true
-
-
- false
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 0da788da27..5006951f11 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -19,6 +19,11 @@
true
+
+ true
+ true
+
+
diff --git a/clean.ps1 b/clean.ps1
index 98dd53af40..955b03c41d 100644
--- a/clean.ps1
+++ b/clean.ps1
@@ -38,4 +38,5 @@ if ($Help) {
exit 0
}
-git clean -dix -e .dotnet/ -e .tools/ -e src/SignalR/clients/ts/FunctionalTests/node_modules/ @GitArguments
+git clean -dix -e .dotnet/ -e .tools/ @GitArguments
+git checkout -- $(git ls-files -d)
diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props
index 80bc739545..f35501e1de 100644
--- a/eng/Baseline.Designer.props
+++ b/eng/Baseline.Designer.props
@@ -2,7 +2,7 @@
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
- 2.2.5
+ 2.2.6
@@ -77,12 +77,12 @@
- 2.2.5
+ 2.2.6
- 2.2.5
+ 2.2.6
@@ -649,6 +649,11 @@
2.2.0
+
+
+ 2.2.6
+
+
2.2.0
@@ -932,7 +937,7 @@
- 2.2.0
+ 2.2.6
@@ -943,13 +948,13 @@
- 2.2.2
+ 2.2.6
-
+
diff --git a/eng/Baseline.xml b/eng/Baseline.xml
index 49a2c044e3..0946044016 100644
--- a/eng/Baseline.xml
+++ b/eng/Baseline.xml
@@ -4,7 +4,7 @@ This file contains a list of all the packages and their versions which were rele
build of ASP.NET Core 2.2.x. Update this list when preparing for a new patch.
-->
-
+
@@ -12,8 +12,8 @@ build of ASP.NET Core 2.2.x. Update this list when preparing for a new patch.
-
-
+
+
@@ -74,6 +74,7 @@ build of ASP.NET Core 2.2.x. Update this list when preparing for a new patch.
+
@@ -100,8 +101,8 @@ build of ASP.NET Core 2.2.x. Update this list when preparing for a new patch.
-
-
+
+
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index 754b192df3..3f6d8d595c 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -84,6 +84,7 @@ and are generated based on the last package release.
+
diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props
index 6c515f6dac..c31e71c5e4 100644
--- a/eng/PatchConfig.props
+++ b/eng/PatchConfig.props
@@ -63,4 +63,8 @@ Later on, this will be checked using this condition:
Microsoft.AspNetCore.Server.IIS;
+
+
+
+
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index 932cec9dce..a54507bbb0 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -73,6 +73,7 @@
+
@@ -87,7 +88,6 @@
-
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index fb70b43094..8ff491b896 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -9,432 +9,432 @@
-->
-
+
https://github.com/aspnet/Blazor
- 7e2a0091dd81c0cb17ceb7bdc4d67aed67dbec66
+ 9045a3861427fc95575c07bd0455c98731567969
-
+
https://github.com/aspnet/AspNetCore-Tooling
- a23fdc5348f411e8fcc29761fb03cf74b1c4f970
+ d95774f01eb5f86d3f9ad00d2341deb74b024f37
-
+
https://github.com/aspnet/AspNetCore-Tooling
- a23fdc5348f411e8fcc29761fb03cf74b1c4f970
+ d95774f01eb5f86d3f9ad00d2341deb74b024f37
-
+
https://github.com/aspnet/AspNetCore-Tooling
- a23fdc5348f411e8fcc29761fb03cf74b1c4f970
+ d95774f01eb5f86d3f9ad00d2341deb74b024f37
-
+
https://github.com/aspnet/AspNetCore-Tooling
- a23fdc5348f411e8fcc29761fb03cf74b1c4f970
+ d95774f01eb5f86d3f9ad00d2341deb74b024f37
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/EntityFrameworkCore
- c916044ea14b17159b7c2cb5f5848bf43f15efc7
+ 751fb9dcf92357260128fc55afeb1e1735791297
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
https://github.com/dotnet/corefx
a28176b5ec68b6da1472934fe9493790d1665cae
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/dotnet/core-setup
- 3ca720b8a2dc8f711a4762729d5c6a223ecc523a
+ 2bb2dcaeffb1dfeda077354449868ddac254bc3d
-
+
https://github.com/dotnet/core-setup
- 3ca720b8a2dc8f711a4762729d5c6a223ecc523a
+ 2bb2dcaeffb1dfeda077354449868ddac254bc3d
-
+
https://github.com/dotnet/core-setup
- 3ca720b8a2dc8f711a4762729d5c6a223ecc523a
+ 2bb2dcaeffb1dfeda077354449868ddac254bc3d
-
+
https://github.com/dotnet/core-setup
- 3ca720b8a2dc8f711a4762729d5c6a223ecc523a
+ 2bb2dcaeffb1dfeda077354449868ddac254bc3d
-
+
https://github.com/dotnet/corefx
- a393ffee970c6d962c5830bb1c6fa4c3a3e268a6
+ c14b80faff694bae4e085bad221e2e83410e5f33
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/dotnet/arcade
- a65d0966dc28861394ce78cfdcb9d5dff370957c
+ fb27fd4d8a2b67d4333e33d4b898c65171c9f3c1
-
+
https://github.com/dotnet/arcade
- a65d0966dc28861394ce78cfdcb9d5dff370957c
+ fb27fd4d8a2b67d4333e33d4b898c65171c9f3c1
-
+
https://github.com/dotnet/arcade
- a65d0966dc28861394ce78cfdcb9d5dff370957c
+ fb27fd4d8a2b67d4333e33d4b898c65171c9f3c1
-
+
https://github.com/aspnet/Extensions
- 340377c1b06f9eb34d018f84bea69119fb622093
+ 8bf6089b4491bc4387cdfec64ab56e7cc39030f8
-
+
https://github.com/dotnet/roslyn
- 17aaaf42dfcf86952acf0c7f788887623e7f4095
+ 342275182023200bcd42b3d9919e984039577b44
diff --git a/eng/Versions.props b/eng/Versions.props
index 41af8c2290..6aee4469b6 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -50,117 +50,117 @@
-->
- 1.0.0-beta.19356.1
+ 1.0.0-beta.19365.4
- 3.3.0-beta2-19359-05
+ 3.3.0-beta2-19363-02
- 3.0.0-preview8-27911-04
- 3.0.0-preview8-27911-04
- 3.0.0-preview8-27911-04
- 2.1.0-preview8-27911-04
+ 3.0.0-preview8-27914-06
+ 3.0.0-preview8-27914-06
+ 3.0.0-preview8-27914-06
+ 2.1.0-preview8-27914-06
- 1.0.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
+ 1.0.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
4.7.0-preview6.19264.9
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 1.7.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
- 4.6.0-preview8.19361.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 1.7.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
+ 4.6.0-preview8.19364.1
- 3.0.0-preview8.19361.1
+ 3.0.0-preview8.19364.1
- 0.10.0-preview8.19355.1
+ 0.10.0-preview8.19365.1
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
+ 3.0.0-preview8.19365.1
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
- 3.0.0-preview8.19361.7
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
+ 3.0.0-preview8.19365.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
- 3.0.0-preview8.19361.5
+ 3.0.0-preview8.19365.2
+ 3.0.0-preview8.19365.2
+ 3.0.0-preview8.19365.2
+ 3.0.0-preview8.19365.2
4.5.0
4.4.0
+ 0.3.0-alpha.19317.1
4.3.0
4.3.2
4.5.2
@@ -201,16 +202,16 @@
3.0.0
3.0.0
3.19.8
- 5.3.0
- 5.3.0
- 5.3.0
+ 5.5.0
+ 5.5.0
+ 5.5.0
2.2.1
1.0.1
3.0.1
3.0.1
11.1.0
1.4.0
- 5.3.0
+ 5.5.0
2.1.1
2.2.0
diff --git a/eng/common/SigningValidation.proj b/eng/common/SigningValidation.proj
index 7045fb6fb9..3d0ac80af3 100644
--- a/eng/common/SigningValidation.proj
+++ b/eng/common/SigningValidation.proj
@@ -3,7 +3,7 @@
+
+
+ $(WorkItemDirectory)
+ $(WorkItemCommand) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --artifacts $(ArtifactsDirectory) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"
+ 4:00
+
+
+
+
+ $(WorkItemDirectory)
+ $(WorkItemCommand) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --artifacts $(ArtifactsDirectory)"
+ 4:00
+
+
+
\ No newline at end of file
diff --git a/eng/common/performance/performance-setup.ps1 b/eng/common/performance/performance-setup.ps1
new file mode 100644
index 0000000000..7e5441f797
--- /dev/null
+++ b/eng/common/performance/performance-setup.ps1
@@ -0,0 +1,91 @@
+Param(
+ [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY,
+ [string] $CoreRootDirectory,
+ [string] $Architecture="x64",
+ [string] $Framework="netcoreapp3.0",
+ [string] $CompilationMode="Tiered",
+ [string] $Repository=$env:BUILD_REPOSITORY_NAME,
+ [string] $Branch=$env:BUILD_SOURCEBRANCH,
+ [string] $CommitSha=$env:BUILD_SOURCEVERSION,
+ [string] $BuildNumber=$env:BUILD_BUILDNUMBER,
+ [string] $RunCategories="coreclr corefx",
+ [string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj",
+ [string] $Kind="micro",
+ [switch] $Internal,
+ [string] $Configurations="CompilationMode=$CompilationMode"
+)
+
+$RunFromPerformanceRepo = ($Repository -eq "dotnet/performance")
+$UseCoreRun = ($CoreRootDirectory -ne [string]::Empty)
+
+$PayloadDirectory = (Join-Path $SourceDirectory "Payload")
+$PerformanceDirectory = (Join-Path $PayloadDirectory "performance")
+$WorkItemDirectory = (Join-Path $SourceDirectory "workitem")
+$ExtraBenchmarkDotNetArguments = "--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true"
+$Creator = $env:BUILD_DEFINITIONNAME
+$PerfLabArguments = ""
+$HelixSourcePrefix = "pr"
+
+$Queue = "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open"
+
+if ($Framework.StartsWith("netcoreapp")) {
+ $Queue = "Windows.10.Amd64.ClientRS4.Open"
+}
+
+if ($Internal) {
+ $Queue = "Windows.10.Amd64.ClientRS5.Perf"
+ $PerfLabArguments = "--upload-to-perflab-container"
+ $ExtraBenchmarkDotNetArguments = ""
+ $Creator = ""
+ $HelixSourcePrefix = "official"
+}
+
+$CommonSetupArguments="--frameworks $Framework --queue $Queue --build-number $BuildNumber --build-configs $Configurations"
+$SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments"
+
+if ($RunFromPerformanceRepo) {
+ $SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments"
+
+ robocopy $SourceDirectory $PerformanceDirectory /E /XD $PayloadDirectory $SourceDirectory\artifacts $SourceDirectory\.git
+}
+else {
+ git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory
+}
+
+if ($UseCoreRun) {
+ $NewCoreRoot = (Join-Path $PayloadDirectory "Core_Root")
+ Move-Item -Path $CoreRootDirectory -Destination $NewCoreRoot
+}
+
+$DocsDir = (Join-Path $PerformanceDirectory "docs")
+robocopy $DocsDir $WorkItemDirectory
+
+# Set variables that we will need to have in future steps
+$ci = $true
+
+. "$PSScriptRoot\..\pipeline-logging-functions.ps1"
+
+# Directories
+Write-PipelineSetVariable -Name 'PayloadDirectory' -Value "$PayloadDirectory" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'PerformanceDirectory' -Value "$PerformanceDirectory" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'WorkItemDirectory' -Value "$WorkItemDirectory" -IsMultiJobVariable $false
+
+# Script Arguments
+Write-PipelineSetVariable -Name 'Python' -Value "py -3" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'ExtraBenchmarkDotNetArguments' -Value "$ExtraBenchmarkDotNetArguments" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'SetupArguments' -Value "$SetupArguments" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'PerfLabArguments' -Value "$PerfLabArguments" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'BDNCategories' -Value "$RunCategories" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'TargetCsproj' -Value "$Csproj" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'Kind' -Value "$Kind" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'Architecture' -Value "$Architecture" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'UseCoreRun' -Value "$UseCoreRun" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'RunFromPerfRepo' -Value "$RunFromPerformanceRepo" -IsMultiJobVariable $false
+
+# Helix Arguments
+Write-PipelineSetVariable -Name 'Creator' -Value "$Creator" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'Queue' -Value "$Queue" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name 'HelixSourcePrefix' -Value "$HelixSourcePrefix" -IsMultiJobVariable $false
+Write-PipelineSetVariable -Name '_BuildConfig' -Value "$Architecture.$Kind.$Framework" -IsMultiJobVariable $false
+
+exit 0
\ No newline at end of file
diff --git a/eng/common/performance/performance-setup.sh b/eng/common/performance/performance-setup.sh
new file mode 100644
index 0000000000..126da5f76d
--- /dev/null
+++ b/eng/common/performance/performance-setup.sh
@@ -0,0 +1,176 @@
+#!/usr/bin/env bash
+
+source_directory=$BUILD_SOURCESDIRECTORY
+core_root_directory=
+architecture=x64
+framework=netcoreapp3.0
+compilation_mode=tiered
+repository=$BUILD_REPOSITORY_NAME
+branch=$BUILD_SOURCEBRANCH
+commit_sha=$BUILD_SOURCEVERSION
+build_number=$BUILD_BUILDNUMBER
+internal=false
+kind="micro"
+run_categories="coreclr corefx"
+csproj="src\benchmarks\micro\MicroBenchmarks.csproj"
+configurations=
+run_from_perf_repo=false
+use_core_run=true
+
+while (($# > 0)); do
+ lowerI="$(echo $1 | awk '{print tolower($0)}')"
+ case $lowerI in
+ --sourcedirectory)
+ source_directory=$2
+ shift 2
+ ;;
+ --corerootdirectory)
+ core_root_directory=$2
+ shift 2
+ ;;
+ --architecture)
+ architecture=$2
+ shift 2
+ ;;
+ --framework)
+ framework=$2
+ shift 2
+ ;;
+ --compilationmode)
+ compilation_mode=$2
+ shift 2
+ ;;
+ --repository)
+ repository=$2
+ shift 2
+ ;;
+ --branch)
+ branch=$2
+ shift 2
+ ;;
+ --commitsha)
+ commit_sha=$2
+ shift 2
+ ;;
+ --buildnumber)
+ build_number=$2
+ shift 2
+ ;;
+ --kind)
+ kind=$2
+ shift 2
+ ;;
+ --runcategories)
+ run_categories=$2
+ shift 2
+ ;;
+ --csproj)
+ csproj=$2
+ shift 2
+ ;;
+ --internal)
+ internal=true
+ shift 1
+ ;;
+ --configurations)
+ configurations=$2
+ shift 2
+ ;;
+ --help)
+ echo "Common settings:"
+ echo " --corerootdirectory Directory where Core_Root exists, if running perf testing with --corerun"
+ echo " --architecture Architecture of the testing being run"
+ echo " --configurations List of key=value pairs that will be passed to perf testing infrastructure."
+ echo " ex: --configurations \"CompilationMode=Tiered OptimzationLevel=PGO\""
+ echo " --help Print help and exit"
+ echo ""
+ echo "Advanced settings:"
+ echo " --framework The framework to run, if not running in master"
+ echo " --compliationmode The compilation mode if not passing --configurations"
+ echo " --sourcedirectory The directory of the sources. Defaults to env:BUILD_SOURCESDIRECTORY"
+ echo " --repository The name of the repository in the / format. Defaults to env:BUILD_REPOSITORY_NAME"
+ echo " --branch The name of the branch. Defaults to env:BUILD_SOURCEBRANCH"
+ echo " --commitsha The commit sha1 to run against. Defaults to env:BUILD_SOURCEVERSION"
+ echo " --buildnumber The build number currently running. Defaults to env:BUILD_BUILDNUMBER"
+ echo " --csproj The relative path to the benchmark csproj whose tests should be run. Defaults to src\benchmarks\micro\MicroBenchmarks.csproj"
+ echo " --kind Related to csproj. The kind of benchmarks that should be run. Defaults to micro"
+ echo " --runcategories Related to csproj. Categories of benchmarks to run. Defaults to \"coreclr corefx\""
+ echo " --internal If the benchmarks are running as an official job."
+ echo ""
+ exit 0
+ ;;
+ esac
+done
+
+if [[ "$repository" == "dotnet/performance" ]]; then
+ run_from_perf_repo=true
+fi
+
+if [ -z "$configurations" ]; then
+ configurations="CompliationMode=$compilation_mode"
+fi
+
+if [ -z "$core_root_directory" ]; then
+ use_core_run=false
+fi
+
+payload_directory=$source_directory/Payload
+performance_directory=$payload_directory/performance
+workitem_directory=$source_directory/workitem
+extra_benchmark_dotnet_arguments="--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true"
+perflab_arguments=
+queue=Ubuntu.1804.Amd64.Open
+creator=$BUILD_DEFINITIONNAME
+helix_source_prefix="pr"
+
+if [[ "$internal" == true ]]; then
+ perflab_arguments="--upload-to-perflab-container"
+ helix_source_prefix="official"
+ creator=
+ extra_benchmark_dotnet_arguments=
+
+ if [[ "$architecture" = "arm64" ]]; then
+ queue=Ubuntu.1804.Arm64.Perf
+ else
+ queue=Ubuntu.1804.Amd64.Perf
+ fi
+fi
+
+common_setup_arguments="--frameworks $framework --queue $queue --build-number $build_number --build-configs $configurations"
+setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments"
+
+if [[ "$run_from_perf_repo" = true ]]; then
+ payload_directory=
+ workitem_directory=$source_directory
+ performance_directory=$workitem_directory
+ setup_arguments="--perf-hash $commit_sha $common_setup_arguments"
+else
+ git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $performance_directory
+
+ docs_directory=$performance_directory/docs
+ mv $docs_directory $workitem_directory
+fi
+
+if [[ "$use_core_run" = true ]]; then
+ new_core_root=$payload_directory/Core_Root
+ mv $core_root_directory $new_core_root
+fi
+
+# Make sure all of our variables are available for future steps
+echo "##vso[task.setvariable variable=UseCoreRun]$use_core_run"
+echo "##vso[task.setvariable variable=Architecture]$architecture"
+echo "##vso[task.setvariable variable=PayloadDirectory]$payload_directory"
+echo "##vso[task.setvariable variable=PerformanceDirectory]$performance_directory"
+echo "##vso[task.setvariable variable=WorkItemDirectory]$workitem_directory"
+echo "##vso[task.setvariable variable=Queue]$queue"
+echo "##vso[task.setvariable variable=SetupArguments]$setup_arguments"
+echo "##vso[task.setvariable variable=Python]python3"
+echo "##vso[task.setvariable variable=PerfLabArguments]$perflab_arguments"
+echo "##vso[task.setvariable variable=ExtraBenchmarkDotNetArguments]$extra_benchmark_dotnet_arguments"
+echo "##vso[task.setvariable variable=BDNCategories]$run_categories"
+echo "##vso[task.setvariable variable=TargetCsproj]$csproj"
+echo "##vso[task.setvariable variable=RunFromPerfRepo]$run_from_perf_repo"
+echo "##vso[task.setvariable variable=Creator]$creator"
+echo "##vso[task.setvariable variable=HelixSourcePrefix]$helix_source_prefix"
+echo "##vso[task.setvariable variable=Kind]$kind"
+echo "##vso[task.setvariable variable=_BuildConfig]$architecture.$kind.$framework"
\ No newline at end of file
diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1
index 7b61376f8a..af5f48aace 100644
--- a/eng/common/pipeline-logging-functions.ps1
+++ b/eng/common/pipeline-logging-functions.ps1
@@ -77,13 +77,14 @@ function Write-PipelineTaskError {
[string]$Name,
[string]$Value,
[switch]$Secret,
- [switch]$AsOutput)
-
+ [switch]$AsOutput,
+ [bool]$IsMultiJobVariable=$true)
+
if($ci) {
Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{
'variable' = $Name
'isSecret' = $Secret
- 'isOutput' = 'true'
+ 'isOutput' = $IsMultiJobVariable
} -AsOutput:$AsOutput
}
}
diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1
index db8a839457..1a91dab037 100644
--- a/eng/common/post-build/trigger-subscriptions.ps1
+++ b/eng/common/post-build/trigger-subscriptions.ps1
@@ -19,14 +19,14 @@ function Get-Headers([string]$accept, [string]$barToken) {
}
# Get all the $SourceRepo subscriptions
-$normalizedSurceRepo = $SourceRepo.Replace('dnceng@', '')
-$getSubscriptionsApiEndpoint = "$maestroEndpoint/api/subscriptions?sourceRepository=$normalizedSurceRepo&api-version=$apiVersion"
+$normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '')
+$getSubscriptionsApiEndpoint = "$maestroEndpoint/api/subscriptions?sourceRepository=$normalizedSourceRepo&api-version=$apiVersion"
$headers = Get-Headers 'application/json' $barToken
$subscriptions = Invoke-WebRequest -Uri $getSubscriptionsApiEndpoint -Headers $headers | ConvertFrom-Json
if (!$subscriptions) {
- Write-Host "No subscriptions found for source repo '$normalizedSurceRepo' in channel '$ChannelId'"
+ Write-Host "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'"
return
}
diff --git a/eng/common/templates/job/performance.yml b/eng/common/templates/job/performance.yml
new file mode 100644
index 0000000000..ef809253d1
--- /dev/null
+++ b/eng/common/templates/job/performance.yml
@@ -0,0 +1,93 @@
+parameters:
+ steps: [] # optional -- any additional steps that need to happen before pulling down the performance repo and sending the performance benchmarks to helix (ie building your repo)
+ variables: [] # optional -- list of additional variables to send to the template
+ jobName: '' # required -- job name
+ displayName: '' # optional -- display name for the job. Will use jobName if not passed
+ pool: '' # required -- name of the Build pool
+ container: '' # required -- name of the container
+ extraSetupParameters: '' # optional -- extra arguments to pass to the setup script
+ frameworks: ['netcoreapp3.0'] # optional -- list of frameworks to run against
+ continueOnError: 'false' # optional -- determines whether to continue the build if the step errors
+ dependsOn: '' # optional -- dependencies of the job
+ timeoutInMinutes: 320 # optional -- timeout for the job
+ enableTelemetry: false # optional -- enable for telemetry
+
+jobs:
+- template: ../jobs/jobs.yml
+ parameters:
+ dependsOn: ${{ parameters.dependsOn }}
+ enableTelemetry: ${{ parameters.enableTelemetry }}
+ enablePublishBuildArtifacts: true
+ continueOnError: ${{ parameters.continueOnError }}
+
+ jobs:
+ - job: '${{ parameters.jobName }}'
+
+ ${{ if ne(parameters.displayName, '') }}:
+ displayName: '${{ parameters.displayName }}'
+ ${{ if eq(parameters.displayName, '') }}:
+ displayName: '${{ parameters.jobName }}'
+
+ timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
+
+ variables:
+
+ - ${{ each variable in parameters.variables }}:
+ - ${{ if ne(variable.name, '') }}:
+ - name: ${{ variable.name }}
+ value: ${{ variable.value }}
+ - ${{ if ne(variable.group, '') }}:
+ - group: ${{ variable.group }}
+
+ - IsInternal: ''
+ - HelixApiAccessToken: ''
+ - HelixPreCommand: ''
+
+ - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+ - ${{ if eq(variables['Agent.Os'], 'Windows_NT') }}:
+ - HelixPreCommand: 'set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"'
+ - IsInternal: -Internal
+ - ${{ if ne(variables['Agent.Os'], 'Windows_NT') }}:
+ - HelixPreCommand: 'export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"'
+ - IsInternal: --internal
+ - group: DotNet-HelixApi-Access
+ - group: dotnet-benchview
+
+ workspace:
+ clean: all
+ pool:
+ ${{ parameters.pool }}
+ container: ${{ parameters.container }}
+ strategy:
+ matrix:
+ ${{ each framework in parameters.frameworks }}:
+ ${{ framework }}:
+ _Framework: ${{ framework }}
+ steps:
+ - checkout: self
+ clean: true
+ # Run all of the steps to setup repo
+ - ${{ each step in parameters.steps }}:
+ - ${{ step }}
+ - powershell: $(Build.SourcesDirectory)\eng\common\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) ${{ parameters.extraSetupParameters }}
+ displayName: Performance Setup (Windows)
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
+ continueOnError: ${{ parameters.continueOnError }}
+ - script: $(Build.SourcesDirectory)/eng/common/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) ${{ parameters.extraSetupParameters }}
+ displayName: Performance Setup (Unix)
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ continueOnError: ${{ parameters.continueOnError }}
+ - script: $(Python) $(PerformanceDirectory)/scripts/ci_setup.py $(SetupArguments)
+ displayName: Run ci setup script
+ # Run perf testing in helix
+ - template: /eng/common/templates/steps/perf-send-to-helix.yml
+ parameters:
+ HelixSource: '$(HelixSourcePrefix)/$(Build.Repository.Name)/$(Build.SourceBranch)' # sources must start with pr/, official/, prodcon/, or agent/
+ HelixType: 'test/performance/$(Kind)/$(_Framework)/$(Architecture)'
+ HelixAccessToken: $(HelixApiAccessToken)
+ HelixTargetQueues: $(Queue)
+ HelixPreCommands: $(HelixPreCommand)
+ Creator: $(Creator)
+ WorkItemTimeout: 4:00 # 4 hours
+ WorkItemDirectory: '$(WorkItemDirectory)' # WorkItemDirectory can not be empty, so we send it some docs to keep it happy
+ CorrelationPayloadDirectory: '$(PayloadDirectory)' # it gets checked out to a folder with shorter path than WorkItemDirectory so we can avoid file name too long exceptions
\ No newline at end of file
diff --git a/eng/common/templates/post-build/channels/internal-servicing.yml b/eng/common/templates/post-build/channels/internal-servicing.yml
index 808d46b17f..50ad724fc0 100644
--- a/eng/common/templates/post-build/channels/internal-servicing.yml
+++ b/eng/common/templates/post-build/channels/internal-servicing.yml
@@ -81,10 +81,10 @@ stages:
/p:IsInternalBuild=$(IsInternalBuild)
/p:RepositoryName=$(Build.Repository.Name)
/p:CommitSha=$(Build.SourceVersion)
+ /p:AzureStorageAccountName=$(ProxyBackedFeedsAccountName)
+ /p:AzureStorageAccountKey=$(dotnetfeed-storage-access-key-1)
+ /p:AzureDevOpsFeedsBaseUrl=$(dotnetfeed-internal-private-feed-url)
/p:NugetPath=$(Agent.BuildDirectory)\Nuget\NuGet.exe
- /p:AzdoTargetFeedPAT='$(dn-bot-dnceng-unviersal-packages-rw)'
- /p:TargetFeedPAT='$(dn-bot-dnceng-unviersal-packages-rw)'
- /p:AzureStorageTargetFeedPAT='$(dotnetfeed-storage-access-key-1)'
/p:BARBuildId=$(BARBuildId)
/p:MaestroApiEndpoint='https://maestro-prod.westus2.cloudapp.azure.com'
/p:BuildAssetRegistryToken='$(MaestroAccessToken)'
@@ -167,4 +167,4 @@ stages:
- template: ../promote-build.yml
parameters:
- ChannelId: ${{ variables.InternalServicing_30_Channel_Id }}
\ No newline at end of file
+ ChannelId: ${{ variables.InternalServicing_30_Channel_Id }}
diff --git a/eng/common/templates/post-build/channels/public-dev-release.yml b/eng/common/templates/post-build/channels/public-dev-release.yml
index 79c6822db7..bdc631016b 100644
--- a/eng/common/templates/post-build/channels/public-dev-release.yml
+++ b/eng/common/templates/post-build/channels/public-dev-release.yml
@@ -77,6 +77,7 @@ stages:
filePath: eng\common\sdk-task.ps1
arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet
/p:ChannelId=$(PublicDevRelease_30_Channel_Id)
+ /p:ArtifactsCategory=.NetCore
/p:IsStableBuild=$(IsStableBuild)
/p:IsInternalBuild=$(IsInternalBuild)
/p:RepositoryName=$(Build.Repository.Name)
diff --git a/eng/common/templates/post-build/channels/public-release.yml b/eng/common/templates/post-build/channels/public-release.yml
index 25923020df..574cb1c2b9 100644
--- a/eng/common/templates/post-build/channels/public-release.yml
+++ b/eng/common/templates/post-build/channels/public-release.yml
@@ -81,10 +81,10 @@ stages:
/p:IsInternalBuild=$(IsInternalBuild)
/p:RepositoryName=$(Build.Repository.Name)
/p:CommitSha=$(Build.SourceVersion)
- /p:NugetPath=$(Agent.BuildDirectory)/Nuget/NuGet.exe
- /p:AzdoTargetFeedPAT='$(dn-bot-dnceng-unviersal-packages-rw)'
- /p:TargetFeedPAT='$(dn-bot-dnceng-unviersal-packages-rw)'
- /p:AzureStorageTargetFeedPAT='$(dotnetfeed-storage-access-key-1)'
+ /p:AzureStorageAccountName=$(ProxyBackedFeedsAccountName)
+ /p:AzureStorageAccountKey=$(dotnetfeed-storage-access-key-1)
+ /p:AzureDevOpsFeedsBaseUrl=$(dotnetfeed-internal-private-feed-url)
+ /p:NugetPath=$(Agent.BuildDirectory)\Nuget\NuGet.exe
/p:BARBuildId=$(BARBuildId)
/p:MaestroApiEndpoint='https://maestro-prod.westus2.cloudapp.azure.com'
/p:BuildAssetRegistryToken='$(MaestroAccessToken)'
diff --git a/eng/common/templates/post-build/channels/public-validation-release.yml b/eng/common/templates/post-build/channels/public-validation-release.yml
index 114477d3ad..f12f402ad9 100644
--- a/eng/common/templates/post-build/channels/public-validation-release.yml
+++ b/eng/common/templates/post-build/channels/public-validation-release.yml
@@ -48,6 +48,7 @@ stages:
filePath: eng\common\sdk-task.ps1
arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet
/p:ChannelId=$(PublicValidationRelease_30_Channel_Id)
+ /p:ArtifactsCategory=.NetCoreValidation
/p:IsStableBuild=$(IsStableBuild)
/p:IsInternalBuild=$(IsInternalBuild)
/p:RepositoryName=$(Build.Repository.Name)
diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml
index 8283467352..42df4ae77e 100644
--- a/eng/common/templates/post-build/common-variables.yml
+++ b/eng/common/templates/post-build/common-variables.yml
@@ -14,5 +14,8 @@ variables:
# Whether the build is internal or not
IsInternalBuild: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }}
+ # Storage account name for proxy-backed feeds
+ ProxyBackedFeedsAccountName: dotnetfeed
+
SourceLinkCLIVersion: 3.0.0
SymbolToolVersion: 1.0.1
diff --git a/eng/common/templates/steps/perf-send-to-helix.yml b/eng/common/templates/steps/perf-send-to-helix.yml
new file mode 100644
index 0000000000..b3ea9acf1f
--- /dev/null
+++ b/eng/common/templates/steps/perf-send-to-helix.yml
@@ -0,0 +1,66 @@
+# Please remember to update the documentation if you make changes to these parameters!
+parameters:
+ HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/
+ HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/'
+ HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number
+ HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues
+ HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group
+ HelixPreCommands: '' # optional -- commands to run before Helix work item execution
+ HelixPostCommands: '' # optional -- commands to run after Helix work item execution
+ WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects
+ CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload
+ IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion
+ DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json
+ DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json
+ EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control
+ WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget."
+ Creator: '' # optional -- if the build is external, use this to specify who is sending the job
+ DisplayNamePrefix: 'Send job to Helix' # optional -- rename the beginning of the displayName of the steps in AzDO
+ condition: succeeded() # optional -- condition for step to execute; defaults to succeeded()
+ continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false
+
+steps:
+ - powershell: $(Build.SourcesDirectory)\eng\common\msbuild.ps1 $(Build.SourcesDirectory)\eng\common\performance\perfhelixpublish.proj /restore /t:Test /bl:$(Build.SourcesDirectory)\artifacts\log\$env:BuildConfig\SendToHelix.binlog
+ displayName: ${{ parameters.DisplayNamePrefix }} (Windows)
+ env:
+ BuildConfig: $(_BuildConfig)
+ HelixSource: ${{ parameters.HelixSource }}
+ HelixType: ${{ parameters.HelixType }}
+ HelixBuild: ${{ parameters.HelixBuild }}
+ HelixTargetQueues: ${{ parameters.HelixTargetQueues }}
+ HelixAccessToken: ${{ parameters.HelixAccessToken }}
+ HelixPreCommands: ${{ parameters.HelixPreCommands }}
+ HelixPostCommands: ${{ parameters.HelixPostCommands }}
+ WorkItemDirectory: ${{ parameters.WorkItemDirectory }}
+ CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }}
+ IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }}
+ DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }}
+ DotNetCliVersion: ${{ parameters.DotNetCliVersion }}
+ EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }}
+ WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }}
+ Creator: ${{ parameters.Creator }}
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT'))
+ continueOnError: ${{ parameters.continueOnError }}
+ - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/performance/perfhelixpublish.proj /restore /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
+ displayName: ${{ parameters.DisplayNamePrefix }} (Unix)
+ env:
+ BuildConfig: $(_BuildConfig)
+ HelixSource: ${{ parameters.HelixSource }}
+ HelixType: ${{ parameters.HelixType }}
+ HelixBuild: ${{ parameters.HelixBuild }}
+ HelixTargetQueues: ${{ parameters.HelixTargetQueues }}
+ HelixAccessToken: ${{ parameters.HelixAccessToken }}
+ HelixPreCommands: ${{ parameters.HelixPreCommands }}
+ HelixPostCommands: ${{ parameters.HelixPostCommands }}
+ WorkItemDirectory: ${{ parameters.WorkItemDirectory }}
+ CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }}
+ IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }}
+ DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }}
+ DotNetCliVersion: ${{ parameters.DotNetCliVersion }}
+ EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }}
+ WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }}
+ Creator: ${{ parameters.Creator }}
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT'))
+ continueOnError: ${{ parameters.continueOnError }}
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index 60741f0390..9abaac015f 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -84,7 +84,7 @@ function Exec-Process([string]$command, [string]$commandArgs) {
return $global:LASTEXITCODE = $process.ExitCode
}
finally {
- # If we didn't finish then an error occured or the user hit ctrl-c. Either
+ # If we didn't finish then an error occurred or the user hit ctrl-c. Either
# way kill the process
if (-not $finished) {
$process.Kill()
@@ -147,7 +147,7 @@ function InitializeDotNetCli([bool]$install) {
# It also ensures that VS msbuild will use the downloaded sdk targets.
$env:PATH = "$dotnetRoot;$env:PATH"
- # Make Sure that our bootstrapped dotnet cli is avaliable in future steps of the Azure Pipelines build
+ # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build
Write-PipelinePrependPath -Path $dotnetRoot
Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0'
Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1'
diff --git a/global.json b/global.json
index 8daa21248f..4c70738470 100644
--- a/global.json
+++ b/global.json
@@ -24,7 +24,7 @@
},
"msbuild-sdks": {
"Yarn.MSBuild": "1.15.2",
- "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19356.1",
- "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19356.1"
+ "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19365.4",
+ "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19365.4"
}
}
diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs b/src/Analyzers/Analyzers/src/BuildServiceProviderAnalzyer.cs
similarity index 52%
rename from src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs
rename to src/Analyzers/Analyzers/src/BuildServiceProviderAnalzyer.cs
index ef6d430eee..edd78751b9 100644
--- a/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs
+++ b/src/Analyzers/Analyzers/src/BuildServiceProviderAnalzyer.cs
@@ -1,41 +1,42 @@
// 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 System.Collections.Concurrent;
-using System.Linq;
+using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
- internal class BuildServiceProviderValidator : StartupDiagnosticValidator
+ internal class BuildServiceProviderValidator
{
- public static BuildServiceProviderValidator CreateAndInitialize(CompilationAnalysisContext context, ConcurrentBag analyses)
+ private readonly StartupAnalysis _context;
+
+ public BuildServiceProviderValidator(StartupAnalysis context)
{
- if (analyses == null)
- {
- throw new ArgumentNullException(nameof(analyses));
- }
+ _context = context;
+ }
- var validator = new BuildServiceProviderValidator();
+ public void AnalyzeSymbol(SymbolAnalysisContext context)
+ {
+ Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType);
+ Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol));
- foreach (var serviceAnalysis in analyses.OfType())
+ var type = (INamedTypeSymbol)context.Symbol;
+
+ foreach (var serviceAnalysis in _context.GetRelatedAnalyses(type))
{
foreach (var serviceItem in serviceAnalysis.Services)
{
if (serviceItem.UseMethod.Name == "BuildServiceProvider")
{
context.ReportDiagnostic(Diagnostic.Create(
- StartupAnalzyer.BuildServiceProviderShouldNotCalledInConfigureServicesMethod,
+ StartupAnalzyer.Diagnostics.BuildServiceProviderShouldNotCalledInConfigureServicesMethod,
serviceItem.Operation.Syntax.GetLocation(),
serviceItem.UseMethod.Name,
serviceAnalysis.ConfigureServicesMethod.Name));
}
}
}
-
- return validator;
}
}
}
diff --git a/src/Analyzers/Analyzers/src/Microsoft.AspNetCore.Analyzers.csproj b/src/Analyzers/Analyzers/src/Microsoft.AspNetCore.Analyzers.csproj
index 2a6daa68bd..feec3324be 100644
--- a/src/Analyzers/Analyzers/src/Microsoft.AspNetCore.Analyzers.csproj
+++ b/src/Analyzers/Analyzers/src/Microsoft.AspNetCore.Analyzers.csproj
@@ -15,6 +15,7 @@
false
false
$(MSBuildProjectName).nuspec
+ Enable
diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs b/src/Analyzers/Analyzers/src/MiddlewareAnalysis.cs
similarity index 51%
rename from src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs
rename to src/Analyzers/Analyzers/src/MiddlewareAnalysis.cs
index 394dd23bff..789dbcc352 100644
--- a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs
+++ b/src/Analyzers/Analyzers/src/MiddlewareAnalysis.cs
@@ -1,18 +1,23 @@
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Analyzers
{
- internal abstract class ConfigureMethodAnalysis : StartupComputedAnalysis
+ internal class MiddlewareAnalysis
{
- protected ConfigureMethodAnalysis(IMethodSymbol configureMethod)
- : base(configureMethod.ContainingType)
+ public MiddlewareAnalysis(IMethodSymbol configureMethod, ImmutableArray middleware)
{
ConfigureMethod = configureMethod;
+ Middleware = middleware;
}
+ public INamedTypeSymbol StartupType => ConfigureMethod.ContainingType;
+
public IMethodSymbol ConfigureMethod { get; }
+
+ public ImmutableArray Middleware { get; }
}
}
diff --git a/src/Analyzers/Analyzers/src/MiddlewareAnalyzer.cs b/src/Analyzers/Analyzers/src/MiddlewareAnalyzer.cs
new file mode 100644
index 0000000000..0a8deb5dc4
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/MiddlewareAnalyzer.cs
@@ -0,0 +1,48 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class MiddlewareAnalyzer
+ {
+ private readonly StartupAnalysisBuilder _context;
+
+ public MiddlewareAnalyzer(StartupAnalysisBuilder context)
+ {
+ _context = context;
+ }
+
+ public void AnalyzeConfigureMethod(OperationBlockStartAnalysisContext context)
+ {
+ var configureMethod = (IMethodSymbol)context.OwningSymbol;
+ var middleware = ImmutableArray.CreateBuilder();
+
+ // Note: this is a simple source-order implementation. We don't attempt perform data flow
+ // analysis in order to determine the actual order in which middleware are ordered.
+ //
+ // This can currently be confused by things like Map(...)
+ context.RegisterOperationAction(context =>
+ {
+ // We're looking for usage of extension methods, so we need to look at the 'this' parameter
+ // rather than invocation.Instance.
+ if (context.Operation is IInvocationOperation invocation &&
+ invocation.Instance == null &&
+ invocation.Arguments.Length >= 1 &&
+ invocation.Arguments[0].Parameter?.Type == _context.StartupSymbols.IApplicationBuilder)
+ {
+ middleware.Add(new MiddlewareItem(invocation));
+ }
+ }, OperationKind.Invocation);
+
+ context.RegisterOperationBlockEndAction(context =>
+ {
+ _context.ReportAnalysis(new MiddlewareAnalysis(configureMethod, middleware.ToImmutable()));
+ });
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareItem.cs b/src/Analyzers/Analyzers/src/MiddlewareItem.cs
similarity index 100%
rename from src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareItem.cs
rename to src/Analyzers/Analyzers/src/MiddlewareItem.cs
diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs b/src/Analyzers/Analyzers/src/OptionsAnalysis.cs
similarity index 53%
rename from src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs
rename to src/Analyzers/Analyzers/src/OptionsAnalysis.cs
index ebe773474b..08f0e11e95 100644
--- a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs
+++ b/src/Analyzers/Analyzers/src/OptionsAnalysis.cs
@@ -1,18 +1,23 @@
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Analyzers
{
- internal abstract class ConfigureServicesMethodAnalysis : StartupComputedAnalysis
+ internal class OptionsAnalysis
{
- protected ConfigureServicesMethodAnalysis(IMethodSymbol configureServicesMethod)
- : base(configureServicesMethod.ContainingType)
+ public OptionsAnalysis(IMethodSymbol configureServicesMethod, ImmutableArray options)
{
ConfigureServicesMethod = configureServicesMethod;
+ Options = options;
}
+ public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType;
+
public IMethodSymbol ConfigureServicesMethod { get; }
+
+ public ImmutableArray Options { get; }
}
}
diff --git a/src/Analyzers/Analyzers/src/OptionsAnalyzer.cs b/src/Analyzers/Analyzers/src/OptionsAnalyzer.cs
new file mode 100644
index 0000000000..a6649c0194
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/OptionsAnalyzer.cs
@@ -0,0 +1,43 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class OptionsAnalyzer
+ {
+ private readonly StartupAnalysisBuilder _context;
+
+ public OptionsAnalyzer(StartupAnalysisBuilder context)
+ {
+ _context = context;
+ }
+
+ public void AnalyzeConfigureServices(OperationBlockStartAnalysisContext context)
+ {
+ var configureServicesMethod = (IMethodSymbol)context.OwningSymbol;
+ var options = ImmutableArray.CreateBuilder();
+ context.RegisterOperationAction(context =>
+ {
+ if (context.Operation is ISimpleAssignmentOperation operation &&
+ operation.Value.ConstantValue.HasValue &&
+ operation.Target is IPropertyReferenceOperation property &&
+ property.Property?.ContainingType?.Name != null &&
+ property.Property.ContainingType.Name.EndsWith("Options"))
+ {
+ options.Add(new OptionsItem(property.Property, operation.Value.ConstantValue.Value));
+ }
+
+ }, OperationKind.SimpleAssignment);
+
+ context.RegisterOperationBlockEndAction(context =>
+ {
+ _context.ReportAnalysis(new OptionsAnalysis(configureServicesMethod, options.ToImmutable()));
+ });
+ }
+ }
+}
diff --git a/src/Analyzers/Analyzers/src/OptionsFacts.cs b/src/Analyzers/Analyzers/src/OptionsFacts.cs
new file mode 100644
index 0000000000..fb89a0224d
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/OptionsFacts.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal static class OptionsFacts
+ {
+ public static bool IsEndpointRoutingExplicitlyDisabled(OptionsAnalysis analysis)
+ {
+ for (var i = 0; i < analysis.Options.Length; i++)
+ {
+ var item = analysis.Options[i];
+ if (string.Equals(item.OptionsType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), SymbolNames.MvcOptions.MetadataName) &&
+ string.Equals(item.Property.Name, SymbolNames.MvcOptions.EnableEndpointRoutingPropertyName, StringComparison.Ordinal))
+ {
+ return item.ConstantValue as bool? == false;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Analyzers/Analyzers/src/OptionsItem.cs b/src/Analyzers/Analyzers/src/OptionsItem.cs
new file mode 100644
index 0000000000..d39850339f
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/OptionsItem.cs
@@ -0,0 +1,22 @@
+// 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 Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class OptionsItem
+ {
+ public OptionsItem(IPropertySymbol property, object constantValue)
+ {
+ Property = property;
+ ConstantValue = constantValue;
+ }
+
+ public INamedTypeSymbol OptionsType => Property.ContainingType;
+
+ public IPropertySymbol Property { get; }
+
+ public object ConstantValue { get; }
+ }
+}
diff --git a/src/Analyzers/Analyzers/src/ServicesAnalysis.cs b/src/Analyzers/Analyzers/src/ServicesAnalysis.cs
new file mode 100644
index 0000000000..2cb58ca5d5
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/ServicesAnalysis.cs
@@ -0,0 +1,23 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class ServicesAnalysis
+ {
+ public ServicesAnalysis(IMethodSymbol configureServicesMethod, ImmutableArray services)
+ {
+ ConfigureServicesMethod = configureServicesMethod;
+ Services = services;
+ }
+
+ public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType;
+
+ public IMethodSymbol ConfigureServicesMethod { get; }
+
+ public ImmutableArray Services { get; }
+ }
+}
diff --git a/src/Analyzers/Analyzers/src/ServicesAnalyzer.cs b/src/Analyzers/Analyzers/src/ServicesAnalyzer.cs
new file mode 100644
index 0000000000..dc79cf4727
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/ServicesAnalyzer.cs
@@ -0,0 +1,43 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class ServicesAnalyzer
+ {
+ private readonly StartupAnalysisBuilder _context;
+
+ public ServicesAnalyzer(StartupAnalysisBuilder context)
+ {
+ _context = context;
+ }
+
+ public void AnalyzeConfigureServices(OperationBlockStartAnalysisContext context)
+ {
+ var configureServicesMethod = (IMethodSymbol)context.OwningSymbol;
+ var services = ImmutableArray.CreateBuilder();
+ context.RegisterOperationAction(context =>
+ {
+ // We're looking for usage of extension methods, so we need to look at the 'this' parameter
+ // rather than invocation.Instance.
+ if (context.Operation is IInvocationOperation invocation &&
+ invocation.Instance == null &&
+ invocation.Arguments.Length >= 1 &&
+ invocation.Arguments[0].Parameter?.Type == _context.StartupSymbols.IServiceCollection)
+ {
+ services.Add(new ServicesItem(invocation));
+ }
+ }, OperationKind.Invocation);
+
+ context.RegisterOperationBlockEndAction(context =>
+ {
+ _context.ReportAnalysis(new ServicesAnalysis(configureServicesMethod, services.ToImmutable()));
+ });
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ServicesItem.cs b/src/Analyzers/Analyzers/src/ServicesItem.cs
similarity index 100%
rename from src/Mvc/Mvc.Analyzers/src/Startup/ServicesItem.cs
rename to src/Analyzers/Analyzers/src/ServicesItem.cs
diff --git a/src/Analyzers/Analyzers/src/StartupAnalysis.cs b/src/Analyzers/Analyzers/src/StartupAnalysis.cs
new file mode 100644
index 0000000000..9ddc03e04e
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/StartupAnalysis.cs
@@ -0,0 +1,56 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class StartupAnalysis
+ {
+ private ImmutableDictionary> _analysesByType;
+
+ public StartupAnalysis(
+ StartupSymbols startupSymbols,
+ ImmutableDictionary> analysesByType)
+ {
+ StartupSymbols = startupSymbols;
+ _analysesByType = analysesByType;
+ }
+
+ public StartupSymbols StartupSymbols { get; }
+
+ public T? GetRelatedSingletonAnalysis(INamedTypeSymbol type) where T : class
+ {
+ if (_analysesByType.TryGetValue(type, out var list))
+ {
+ for (var i = 0; i < list.Length; i++)
+ {
+ if (list[i] is T item)
+ {
+ return item;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public ImmutableArray GetRelatedAnalyses(INamedTypeSymbol type) where T : class
+ {
+ var items = ImmutableArray.CreateBuilder();
+ if (_analysesByType.TryGetValue(type, out var list))
+ {
+ for (var i = 0; i < list.Length; i++)
+ {
+ if (list[i] is T item)
+ {
+ items.Add(item);
+ }
+ }
+ }
+
+ return items.ToImmutable();
+ }
+ }
+}
diff --git a/src/Analyzers/Analyzers/src/StartupAnalysisBuilder.cs b/src/Analyzers/Analyzers/src/StartupAnalysisBuilder.cs
new file mode 100644
index 0000000000..e5b1217dd7
--- /dev/null
+++ b/src/Analyzers/Analyzers/src/StartupAnalysisBuilder.cs
@@ -0,0 +1,71 @@
+// 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.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.AspNetCore.Analyzers
+{
+ internal class StartupAnalysisBuilder
+ {
+ private readonly Dictionary> _analysesByType;
+ private readonly StartupAnalzyer _analyzer;
+ private readonly object _lock;
+
+ public StartupAnalysisBuilder(StartupAnalzyer analyzer, StartupSymbols startupSymbols)
+ {
+ _analyzer = analyzer;
+ StartupSymbols = startupSymbols;
+
+ _analysesByType = new Dictionary>();
+ _lock = new object();
+ }
+
+ public StartupSymbols StartupSymbols { get; }
+
+ public StartupAnalysis Build()
+ {
+ lock (_lock)
+ {
+ return new StartupAnalysis(
+ StartupSymbols,
+ _analysesByType.ToImmutableDictionary(
+ k => k.Key,
+ v => v.Value.ToImmutableArray()));
+ }
+ }
+
+ public void ReportAnalysis(ServicesAnalysis analysis)
+ {
+ ReportAnalysisCore(analysis.StartupType, analysis);
+ _analyzer.OnServicesAnalysisCompleted(analysis);
+ }
+
+ public void ReportAnalysis(OptionsAnalysis analysis)
+ {
+ ReportAnalysisCore(analysis.StartupType, analysis);
+ _analyzer.OnOptionsAnalysisCompleted(analysis);
+ }
+
+ public void ReportAnalysis(MiddlewareAnalysis analysis)
+ {
+ ReportAnalysisCore(analysis.StartupType, analysis);
+ _analyzer.OnMiddlewareAnalysisCompleted(analysis);
+ }
+
+ private void ReportAnalysisCore(INamedTypeSymbol type, object analysis)
+ {
+ lock (_lock)
+ {
+ if (!_analysesByType.TryGetValue(type, out var list))
+ {
+ list = new List
-
-
- True
- True
- Resources.resx
-
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
-
diff --git a/src/Components/Analyzers/src/Resources.Designer.cs b/src/Components/Analyzers/src/Resources.Designer.cs
deleted file mode 100644
index 13c55946a3..0000000000
--- a/src/Components/Analyzers/src/Resources.Designer.cs
+++ /dev/null
@@ -1,180 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace Microsoft.AspNetCore.Components.Analyzers {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.Components.Analyzers.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameters with CaptureUnmatchedValues must be a correct type..
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Description {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesHasWrongType_Description", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter '{0}' defines CaptureUnmatchedValues but has an unsupported type '{1}'. Use a type assignable from '{2}'..
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Format {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesHasWrongType_Format", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter with CaptureUnmatchedValues has the wrong type.
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesHasWrongType_Title {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesHasWrongType_Title", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Components may only define a single parameter with CaptureUnmatchedValues..
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesMustBeUnique_Description {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesMustBeUnique_Description", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component type '{0}' defines properties multiple parameters with CaptureUnmatchedValues. Properties: {1}{2}.
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesMustBeUnique_Format {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesMustBeUnique_Format", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component has multiple CaptureUnmatchedValues parameters.
- ///
- internal static string ComponentParameterCaptureUnmatchedValuesMustBeUnique_Title {
- get {
- return ResourceManager.GetString("ComponentParameterCaptureUnmatchedValuesMustBeUnique_Title", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameters should have public setters..
- ///
- internal static string ComponentParameterSettersShouldBePublic_Description {
- get {
- return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Description", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter '{0}' should have a public setter..
- ///
- internal static string ComponentParameterSettersShouldBePublic_Format {
- get {
- return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Format", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter should have public setters..
- ///
- internal static string ComponentParameterSettersShouldBePublic_Title {
- get {
- return ResourceManager.GetString("ComponentParameterSettersShouldBePublic_Title", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter '{0}' should be public..
- ///
- internal static string ComponentParameterShouldBePublic_Format {
- get {
- return ResourceManager.GetString("ComponentParameterShouldBePublic_Format", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameter should be public..
- ///
- internal static string ComponentParameterShouldBePublic_Title {
- get {
- return ResourceManager.GetString("ComponentParameterShouldBePublic_Title", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Component parameters should be public..
- ///
- internal static string ComponentParametersShouldBePublic_Description {
- get {
- return ResourceManager.GetString("ComponentParametersShouldBePublic_Description", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Make component parameters public..
- ///
- internal static string ComponentParametersShouldBePublic_FixTitle {
- get {
- return ResourceManager.GetString("ComponentParametersShouldBePublic_FixTitle", resourceCulture);
- }
- }
- }
-}
diff --git a/src/Components/Analyzers/src/Resources.resx b/src/Components/Analyzers/src/Resources.resx
index 1c4319cb5b..648adb2c3b 100644
--- a/src/Components/Analyzers/src/Resources.resx
+++ b/src/Components/Analyzers/src/Resources.resx
@@ -156,4 +156,13 @@
Make component parameters public.
+
+ Component parameters should not be set outside of their declared component. Doing so may result in unexpected behavior at runtime.
+
+
+ Component parameter '{0}' should not be set outside of its component.
+
+
+ Component parameter should not be set outside of its component.
+
\ No newline at end of file
diff --git a/src/Components/Analyzers/test/ComponentParameterUsageAnalyzerTest.cs b/src/Components/Analyzers/test/ComponentParameterUsageAnalyzerTest.cs
new file mode 100644
index 0000000000..db664a2e76
--- /dev/null
+++ b/src/Components/Analyzers/test/ComponentParameterUsageAnalyzerTest.cs
@@ -0,0 +1,298 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using TestHelper;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components.Analyzers
+{
+ public class ComponentParameterUsageAnalyzerTest : DiagnosticVerifier
+ {
+ public ComponentParameterUsageAnalyzerTest()
+ {
+ ComponentTestSource = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TestComponent : IComponent
+ {{
+ [Parameter] public string TestProperty {{ get; set; }}
+ public string NonParameter {{ get; set; }}
+ }}
+ }}" + ComponentsTestDeclarations.Source;
+ }
+
+ private string ComponentTestSource { get; }
+
+ [Fact]
+ public void ComponentPropertySimpleAssignment_Warns()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class OtherComponent : IComponent
+ {{
+ private TestComponent _testComponent;
+ void Render()
+ {{
+ _testComponent = new TestComponent();
+ _testComponent.TestProperty = ""Hello World"";
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test,
+ new DiagnosticResult
+ {
+ Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
+ Message = "Component parameter 'TestProperty' should not be set outside of its component.",
+ Severity = DiagnosticSeverity.Warning,
+ Locations = new[]
+ {
+ new DiagnosticResultLocation("Test0.cs", 11, 17)
+ }
+ });
+ }
+
+ [Fact]
+ public void ComponentPropertyCoalesceAssignment__Warns()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class OtherComponent : IComponent
+ {{
+ private TestComponent _testComponent;
+ void Render()
+ {{
+ _testComponent = new TestComponent();
+ _testComponent.TestProperty ??= ""Hello World"";
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test,
+ new DiagnosticResult
+ {
+ Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
+ Message = "Component parameter 'TestProperty' should not be set outside of its component.",
+ Severity = DiagnosticSeverity.Warning,
+ Locations = new[]
+ {
+ new DiagnosticResultLocation("Test0.cs", 11, 17)
+ }
+ });
+ }
+
+ [Fact]
+ public void ComponentPropertyCompoundAssignment__Warns()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class OtherComponent : IComponent
+ {{
+ private TestComponent _testComponent;
+ void Render()
+ {{
+ _testComponent = new TestComponent();
+ _testComponent.TestProperty += ""Hello World"";
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test,
+ new DiagnosticResult
+ {
+ Id = DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent.Id,
+ Message = "Component parameter 'TestProperty' should not be set outside of its component.",
+ Severity = DiagnosticSeverity.Warning,
+ Locations = new[]
+ {
+ new DiagnosticResultLocation("Test0.cs", 11, 17)
+ }
+ });
+ }
+
+ [Fact]
+ public void ComponentPropertyExpression_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName
+ {{
+ void Method()
+ {{
+ System.IO.Console.WriteLine(new TestComponent().TestProperty);
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void ComponentPropertyExpressionInStatement_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName
+ {{
+ void Method()
+ {{
+ var testComponent = new TestComponent();
+ for (var i = 0; i < testComponent.TestProperty.Length; i++)
+ {{
+ }}
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void RetrievalOfComponentPropertyValueInAssignment_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName
+ {{
+ void Method()
+ {{
+ var testComponent = new TestComponent();
+ AnotherProperty = testComponent.TestProperty;
+ }}
+
+ public string AnotherProperty {{ get; set; }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void ShadowedComponentPropertyAssignment_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName
+ {{
+ void Method()
+ {{
+ var testComponent = new InheritedComponent();
+ testComponent.TestProperty = ""Hello World"";
+ }}
+ }}
+
+ class InheritedComponent : TestComponent
+ {{
+ public new string TestProperty {{ get; set; }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void InheritedImplicitComponentPropertyAssignment_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName : TestComponent
+ {{
+ void Method()
+ {{
+ this.TestProperty = ""Hello World"";
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void ImplicitComponentPropertyAssignment_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class TypeName : IComponent
+ {{
+ void Method()
+ {{
+ TestProperty = ""Hello World"";
+ }}
+
+ [Parameter] public string TestProperty {{ get; set; }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void ComponentPropertyAssignment_NonParameter_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class OtherComponent : IComponent
+ {{
+ private TestComponent _testComponent;
+ void Render()
+ {{
+ _testComponent = new TestComponent();
+ _testComponent.NonParameter = ""Hello World"";
+ }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ [Fact]
+ public void NonComponentPropertyAssignment_Ignores()
+ {
+ var test = $@"
+ namespace ConsoleApplication1
+ {{
+ using {typeof(ParameterAttribute).Namespace};
+ class OtherComponent : IComponent
+ {{
+ private SomethingElse _testNonComponent;
+ void Render()
+ {{
+ _testNonComponent = new NotAComponent();
+ _testNonComponent.TestProperty = ""Hello World"";
+ }}
+ }}
+ class NotAComponent
+ {{
+ [Parameter] public string TestProperty {{ get; set; }}
+ }}
+ }}" + ComponentTestSource;
+
+ VerifyCSharpDiagnostic(test);
+ }
+
+ protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => new ComponentParameterUsageAnalyzer();
+ }
+}
diff --git a/src/Components/Analyzers/test/ComponentsTestDeclarations.cs b/src/Components/Analyzers/test/ComponentsTestDeclarations.cs
index e09f156ff4..3c71a82566 100644
--- a/src/Components/Analyzers/test/ComponentsTestDeclarations.cs
+++ b/src/Components/Analyzers/test/ComponentsTestDeclarations.cs
@@ -16,6 +16,10 @@ namespace Microsoft.AspNetCore.Components.Analyzers
public class {typeof(CascadingParameterAttribute).Name} : System.Attribute
{{
}}
+
+ public interface {typeof(IComponent).Name}
+ {{
+ }}
}}
";
}
diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs
index 7c6500f008..3897a0bf62 100644
--- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs
+++ b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs
@@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
public System.Threading.Tasks.Task AddComponentAsync(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
- public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
+ public override System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
protected override void Dispose(bool disposing) { }
protected override void HandleException(System.Exception exception) { }
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }
diff --git a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs
index 84e84fb609..528816e492 100644
--- a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs
+++ b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs
@@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
}
///
- public override Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
+ public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
{
// Be sure we only run one event handler at once. Although they couldn't run
// simultaneously anyway (there's only one thread), they could run nested on
@@ -183,12 +183,12 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
readonly struct IncomingEventInfo
{
- public readonly int EventHandlerId;
+ public readonly ulong EventHandlerId;
public readonly EventFieldInfo EventFieldInfo;
public readonly UIEventArgs EventArgs;
public readonly TaskCompletionSource TaskCompletionSource;
- public IncomingEventInfo(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
+ public IncomingEventInfo(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
{
EventHandlerId = eventHandlerId;
EventFieldInfo = eventFieldInfo;
diff --git a/src/Components/Blazor/BlazorExtension/src/Microsoft.VisualStudio.BlazorExtension.csproj b/src/Components/Blazor/BlazorExtension/src/Microsoft.VisualStudio.BlazorExtension.csproj
index b803dd969f..10f7b7f541 100644
--- a/src/Components/Blazor/BlazorExtension/src/Microsoft.VisualStudio.BlazorExtension.csproj
+++ b/src/Components/Blazor/BlazorExtension/src/Microsoft.VisualStudio.BlazorExtension.csproj
@@ -26,6 +26,9 @@
true
false
true
+
+
+ false
diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
index 986d57321a..5baad704a9 100644
--- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
+++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor
@@ -11,5 +11,5 @@
@code {
// Demonstrates how a parent component can supply parameters
- [Parameter] string Title { get; set; }
+ [Parameter] public string Title { get; set; }
}
diff --git a/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor b/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor
index 9024a800a7..9a741bddfb 100644
--- a/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor
+++ b/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor
@@ -44,7 +44,7 @@ else
}
@code {
- [Parameter] DateTime StartDate { get; set; }
+ [Parameter] public DateTime StartDate { get; set; }
WeatherForecast[] forecasts;
diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs
index 4c25bec3fa..be1a95b1dd 100644
--- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs
+++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs
@@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Components
public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
[Microsoft.AspNetCore.Components.ParameterAttribute]
public T Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
- public void Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
+ public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterCollection parameters) { throw null; }
}
@@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Components
public System.Type Page { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
[Microsoft.AspNetCore.Components.ParameterAttribute]
public System.Collections.Generic.IDictionary PageParameters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
- public void Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
+ public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterCollection parameters) { throw null; }
}
}
@@ -255,7 +255,7 @@ namespace Microsoft.AspNetCore.Components.Routing
public RenderFragment ChildContent { get; set; }
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.Routing.NavLinkMatch Match { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
- public void Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
+ public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public void Dispose() { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterCollection parameters) { throw null; }
}
@@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.Components.Routing
public Microsoft.AspNetCore.Components.RenderFragment NotAuthorizedContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
[Microsoft.AspNetCore.Components.ParameterAttribute]
public Microsoft.AspNetCore.Components.RenderFragment AuthorizingContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; }}
- public void Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
+ public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
public void Dispose() { }
protected virtual void Render(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder, System.Type handler, System.Collections.Generic.IDictionary parameters) { }
public System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterCollection parameters) { throw null; }
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 9405897b79..9cf260bf92 100644
--- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs
+++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs
@@ -25,6 +25,53 @@ namespace Microsoft.AspNetCore.Components
public static partial class BindAttributes
{
}
+ public static partial class BindConverter
+ {
+ public static bool FormatValue(bool value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTime value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTime value, string format, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTimeOffset value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTimeOffset value, string format, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(decimal value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(double value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(int value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(long value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static bool? FormatValue(bool? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTimeOffset? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTimeOffset? value, string format, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTime? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(System.DateTime? value, string format, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(decimal? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(double? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(int? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(long? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(float? value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(float value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static string FormatValue(string value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static object FormatValue(T value, System.Globalization.CultureInfo culture = null) { throw null; }
+ public static bool TryConvertToBool(object obj, System.Globalization.CultureInfo culture, out bool value) { throw null; }
+ public static bool TryConvertToDateTime(object obj, System.Globalization.CultureInfo culture, out System.DateTime value) { throw null; }
+ public static bool TryConvertToDateTime(object obj, System.Globalization.CultureInfo culture, string format, out System.DateTime value) { throw null; }
+ public static bool TryConvertToDateTimeOffset(object obj, System.Globalization.CultureInfo culture, out System.DateTimeOffset value) { throw null; }
+ public static bool TryConvertToDateTimeOffset(object obj, System.Globalization.CultureInfo culture, string format, out System.DateTimeOffset value) { throw null; }
+ public static bool TryConvertToDecimal(object obj, System.Globalization.CultureInfo culture, out decimal value) { throw null; }
+ public static bool TryConvertToDouble(object obj, System.Globalization.CultureInfo culture, out double value) { throw null; }
+ public static bool TryConvertToFloat(object obj, System.Globalization.CultureInfo culture, out float value) { throw null; }
+ public static bool TryConvertToInt(object obj, System.Globalization.CultureInfo culture, out int value) { throw null; }
+ public static bool TryConvertToLong(object obj, System.Globalization.CultureInfo culture, out long value) { throw null; }
+ public static bool TryConvertToNullableBool(object obj, System.Globalization.CultureInfo culture, out bool? value) { throw null; }
+ public static bool TryConvertToNullableDateTime(object obj, System.Globalization.CultureInfo culture, out System.DateTime? value) { throw null; }
+ public static bool TryConvertToNullableDateTime(object obj, System.Globalization.CultureInfo culture, string format, out System.DateTime? value) { throw null; }
+ public static bool TryConvertToNullableDateTimeOffset(object obj, System.Globalization.CultureInfo culture, out System.DateTimeOffset? value) { throw null; }
+ public static bool TryConvertToNullableDateTimeOffset(object obj, System.Globalization.CultureInfo culture, string format, out System.DateTimeOffset? value) { throw null; }
+ public static bool TryConvertToNullableDecimal(object obj, System.Globalization.CultureInfo culture, out decimal? value) { throw null; }
+ public static bool TryConvertToNullableDouble(object obj, System.Globalization.CultureInfo culture, out double? value) { throw null; }
+ public static bool TryConvertToNullableFloat(object obj, System.Globalization.CultureInfo culture, out float? value) { throw null; }
+ public static bool TryConvertToNullableInt(object obj, System.Globalization.CultureInfo culture, out int? value) { throw null; }
+ public static bool TryConvertToNullableLong(object obj, System.Globalization.CultureInfo culture, out long? value) { throw null; }
+ public static bool TryConvertToString(object obj, System.Globalization.CultureInfo culture, out string value) { throw null; }
+ public static bool TryConvertTo(object obj, System.Globalization.CultureInfo culture, out T value) { throw null; }
+ }
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public sealed partial class BindElementAttribute : System.Attribute
{
@@ -63,7 +110,7 @@ namespace Microsoft.AspNetCore.Components
protected virtual void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { }
protected System.Threading.Tasks.Task InvokeAsync(System.Action workItem) { throw null; }
protected System.Threading.Tasks.Task InvokeAsync(System.Func workItem) { throw null; }
- void Microsoft.AspNetCore.Components.IComponent.Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
+ void Microsoft.AspNetCore.Components.IComponent.Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; }
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(Microsoft.AspNetCore.Components.EventCallbackWorkItem callback, object arg) { throw null; }
protected virtual void OnAfterRender() { }
@@ -104,6 +151,11 @@ namespace Microsoft.AspNetCore.Components
public string __internalId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
+ public readonly partial struct ElementReference
+ {
+ private readonly object _dummy;
+ }
+ [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct EventCallback
{
private readonly object _dummy;
@@ -307,7 +359,7 @@ namespace Microsoft.AspNetCore.Components
}
public partial interface IComponent
{
- void Configure(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
+ void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle);
System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterCollection parameters);
}
public partial interface IComponentContext
@@ -347,7 +399,7 @@ namespace Microsoft.AspNetCore.Components
{
protected LayoutComponentBase() { }
[Microsoft.AspNetCore.Components.ParameterAttribute]
- protected Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
+ public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct MarkupString
@@ -672,7 +724,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
private readonly object _dummy;
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
- public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
+ public Microsoft.AspNetCore.Components.RenderTree.ArrayRange DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
@@ -683,7 +735,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; }
- public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
+ public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
protected abstract void HandleException(System.Exception exception);
@@ -716,7 +768,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
public ArrayRange(T[] array, int count) { throw null; }
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange Clone() { throw null; }
}
- public partial class RenderTreeBuilder
+ public partial class RenderTreeBuilder : System.IDisposable
{
public const string ChildContent = "ChildContent";
public RenderTreeBuilder(Microsoft.AspNetCore.Components.Rendering.Renderer renderer) { }
@@ -747,6 +799,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
public void OpenElement(int sequence, string elementName) { }
public void SetKey(object value) { }
public void SetUpdatesAttributeName(string updatesAttributeName) { }
+ void System.IDisposable.Dispose() { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct RenderTreeDiff
@@ -781,17 +834,17 @@ namespace Microsoft.AspNetCore.Components.RenderTree
PermutationListEntry = 9,
PermutationListEnd = 10,
}
- public enum RenderTreeFrameType
+ public enum RenderTreeFrameType : short
{
- None = 0,
- Element = 1,
- Text = 2,
- Attribute = 3,
- Component = 4,
- Region = 5,
- ElementReferenceCapture = 6,
- ComponentReferenceCapture = 7,
- Markup = 8,
+ None = (short)0,
+ Element = (short)1,
+ Text = (short)2,
+ Attribute = (short)3,
+ Component = (short)4,
+ Region = (short)5,
+ ElementReferenceCapture = (short)6,
+ ComponentReferenceCapture = (short)7,
+ Markup = (short)8,
}
}
namespace Microsoft.AspNetCore.Components.Routing
diff --git a/src/Components/Components/src/Auth/AuthorizeViewCore.cs b/src/Components/Components/src/Auth/AuthorizeViewCore.cs
index 07e2cd1c48..4b1f2f581f 100644
--- a/src/Components/Components/src/Auth/AuthorizeViewCore.cs
+++ b/src/Components/Components/src/Auth/AuthorizeViewCore.cs
@@ -102,10 +102,27 @@ namespace Microsoft.AspNetCore.Components
private async Task IsAuthorizedAsync(ClaimsPrincipal user)
{
var authorizeData = GetAuthorizeData();
+ EnsureNoAuthenticationSchemeSpecified(authorizeData);
+
var policy = await AuthorizationPolicy.CombineAsync(
AuthorizationPolicyProvider, authorizeData);
var result = await AuthorizationService.AuthorizeAsync(user, Resource, policy);
return result.Succeeded;
}
+
+ private static void EnsureNoAuthenticationSchemeSpecified(IAuthorizeData[] authorizeData)
+ {
+ // It's not meaningful to specify a nonempty scheme, since by the time Components
+ // authorization runs, we already have a specific ClaimsPrincipal (we're stateful).
+ // To avoid any confusion, ensure the developer isn't trying to specify a scheme.
+ for (var i = 0; i < authorizeData.Length; i++)
+ {
+ var entry = authorizeData[i];
+ if (!string.IsNullOrEmpty(entry.AuthenticationSchemes))
+ {
+ throw new NotSupportedException($"The authorization data specifies an authentication scheme with value '{entry.AuthenticationSchemes}'. Authentication schemes cannot be specified for components.");
+ }
+ }
+ }
}
}
diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs
new file mode 100644
index 0000000000..f77689bde2
--- /dev/null
+++ b/src/Components/Components/src/BindConverter.cs
@@ -0,0 +1,1418 @@
+// 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 System.Collections.Concurrent;
+using System.ComponentModel;
+using System.Globalization;
+using System.Reflection;
+
+namespace Microsoft.AspNetCore.Components
+{
+ ///
+ /// Performs conversions during binding.
+ ///
+ //
+ // Perf: our conversion routines present a regular API surface that allows us to specialize on types to avoid boxing.
+ // for instance, many of these types could be cast to IFormattable to do the appropriate formatting, but that's going
+ // to allocate.
+ public static class BindConverter
+ {
+ private static object BoxedTrue = true;
+ private static object BoxedFalse = false;
+
+ private delegate object BindFormatter(T value, CultureInfo culture);
+ private delegate object BindFormatterWithFormat(T value, CultureInfo culture, string format);
+
+ internal delegate bool BindParser(object obj, CultureInfo culture, out T value);
+ internal delegate bool BindParserWithFormat(object obj, CultureInfo culture, string format, out T value);
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(string value, CultureInfo culture = null) => FormatStringValueCore(value, culture);
+
+ private static string FormatStringValueCore(string value, CultureInfo culture)
+ {
+ return value;
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static bool FormatValue(bool value, CultureInfo culture = null)
+ {
+ // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
+ // to work.
+ return value;
+ }
+
+ // Used with generics
+ private static object FormatBoolValueCore(bool value, CultureInfo culture)
+ {
+ // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
+ // to work.
+ return value ? BoxedTrue : BoxedFalse;
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static bool? FormatValue(bool? value, CultureInfo culture = null)
+ {
+ // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
+ // to work.
+ return value == null ? (bool?)null : value.Value;
+ }
+
+ // Used with generics
+ private static object FormatNullableBoolValueCore(bool? value, CultureInfo culture)
+ {
+ // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
+ // to work.
+ return value == null ? null : value.Value ? BoxedTrue : BoxedFalse;
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(int value, CultureInfo culture = null) => FormatIntValueCore(value, culture);
+
+ private static string FormatIntValueCore(int value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(int? value, CultureInfo culture = null) => FormatNullableIntValueCore(value, culture);
+
+ private static string FormatNullableIntValueCore(int? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(long value, CultureInfo culture = null) => FormatLongValueCore(value, culture);
+
+ private static string FormatLongValueCore(long value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(long? value, CultureInfo culture = null) => FormatNullableLongValueCore(value, culture);
+
+ private static string FormatNullableLongValueCore(long? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(float value, CultureInfo culture = null) => FormatFloatValueCore(value, culture);
+
+ private static string FormatFloatValueCore(float value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(float? value, CultureInfo culture = null) => FormatNullableFloatValueCore(value, culture);
+
+ private static string FormatNullableFloatValueCore(float? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(double value, CultureInfo culture = null) => FormatDoubleValueCore(value, culture);
+
+ private static string FormatDoubleValueCore(double value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(double? value, CultureInfo culture = null) => FormatNullableDoubleValueCore(value, culture);
+
+ private static string FormatNullableDoubleValueCore(double? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided for inclusion in an attribute.
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(decimal value, CultureInfo culture = null) => FormatDecimalValueCore(value, culture);
+
+ private static string FormatDecimalValueCore(decimal value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(decimal? value, CultureInfo culture = null) => FormatNullableDecimalValueCore(value, culture);
+
+ private static string FormatNullableDecimalValueCore(decimal? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTime value, CultureInfo culture = null) => FormatDateTimeValueCore(value, format: null, culture);
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ /// The format to use. Provided to .
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTime value, string format, CultureInfo culture = null) => FormatDateTimeValueCore(value, format, culture);
+
+ private static string FormatDateTimeValueCore(DateTime value, string format, CultureInfo culture)
+ {
+ if (format != null)
+ {
+ return value.ToString(format, culture ?? CultureInfo.CurrentCulture);
+ }
+
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ private static string FormatDateTimeValueCore(DateTime value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTime? value, CultureInfo culture = null) => FormatNullableDateTimeValueCore(value, format: null, culture);
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ /// The format to use. Provided to .
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTime? value, string format, CultureInfo culture = null) => FormatNullableDateTimeValueCore(value, format, culture);
+
+ private static string FormatNullableDateTimeValueCore(DateTime? value, string format, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (format != null)
+ {
+ return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture);
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ private static string FormatNullableDateTimeValueCore(DateTime? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTimeOffset value, CultureInfo culture = null) => FormatDateTimeOffsetValueCore(value, format: null, culture);
+
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ /// The format to use. Provided to .
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTimeOffset value, string format, CultureInfo culture = null) => FormatDateTimeOffsetValueCore(value, format, culture);
+
+ private static string FormatDateTimeOffsetValueCore(DateTimeOffset value, string format, CultureInfo culture)
+ {
+ if (format != null)
+ {
+ return value.ToString(format, culture ?? CultureInfo.CurrentCulture);
+ }
+
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ private static string FormatDateTimeOffsetValueCore(DateTimeOffset value, CultureInfo culture)
+ {
+ return value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTimeOffset? value, CultureInfo culture = null) => FormatNullableDateTimeOffsetValueCore(value, format: null, culture);
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ /// The format to use. Provided to .
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static string FormatValue(DateTimeOffset? value, string format, CultureInfo culture = null) => FormatNullableDateTimeOffsetValueCore(value, format, culture);
+
+ private static string FormatNullableDateTimeOffsetValueCore(DateTimeOffset? value, string format, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (format != null)
+ {
+ return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture);
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ private static string FormatNullableDateTimeOffsetValueCore(DateTimeOffset? value, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
+ }
+
+ private static string FormatEnumValueCore(T value, CultureInfo culture) where T : struct, Enum
+ {
+ return value.ToString(); // The overload that acccepts a culture is [Obsolete]
+ }
+
+ private static string FormatNullableEnumValueCore(T? value, CultureInfo culture) where T : struct, Enum
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value.Value.ToString(); // The overload that acccepts a culture is [Obsolete]
+ }
+
+ ///
+ /// Formats the provided as a .
+ ///
+ /// The value to format.
+ ///
+ /// The to use while formatting. Defaults to .
+ ///
+ /// The formatted value.
+ public static object FormatValue(T value, CultureInfo culture = null)
+ {
+ var formatter = FormatterDelegateCache.Get();
+ return formatter(value, culture);
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToString(object obj, CultureInfo culture, out string value)
+ {
+ return ConvertToStringCore(obj, culture, out value);
+ }
+
+ internal readonly static BindParser ConvertToString = ConvertToStringCore;
+
+ private static bool ConvertToStringCore(object obj, CultureInfo culture, out string value)
+ {
+ // We expect the input to already be a string.
+ value = (string)obj;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToBool(object obj, CultureInfo culture, out bool value)
+ {
+ return ConvertToBoolCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableBool(object obj, CultureInfo culture, out bool? value)
+ {
+ return ConvertToNullableBoolCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToBool = ConvertToBoolCore;
+ internal static BindParser ConvertToNullableBool = ConvertToNullableBoolCore;
+
+ private static bool ConvertToBoolCore(object obj, CultureInfo culture, out bool value)
+ {
+ // We expect the input to already be a bool.
+ value = (bool)obj;
+ return true;
+ }
+
+ private static bool ConvertToNullableBoolCore(object obj, CultureInfo culture, out bool? value)
+ {
+ // We expect the input to already be a bool.
+ value = (bool?)obj;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToInt(object obj, CultureInfo culture, out int value)
+ {
+ return ConvertToIntCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableInt(object obj, CultureInfo culture, out int? value)
+ {
+ return ConvertToNullableIntCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToInt = ConvertToIntCore;
+ internal static BindParser ConvertToNullableInt = ConvertToNullableIntCore;
+
+ private static bool ConvertToIntCore(object obj, CultureInfo culture, out int value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableIntCore(object obj, CultureInfo culture, out int? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToLong(object obj, CultureInfo culture, out long value)
+ {
+ return ConvertToLongCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableLong(object obj, CultureInfo culture, out long? value)
+ {
+ return ConvertToNullableLongCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToLong = ConvertToLongCore;
+ internal static BindParser ConvertToNullableLong = ConvertToNullableLongCore;
+
+ private static bool ConvertToLongCore(object obj, CultureInfo culture, out long value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableLongCore(object obj, CultureInfo culture, out long? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToFloat(object obj, CultureInfo culture, out float value)
+ {
+ return ConvertToFloatCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableFloat(object obj, CultureInfo culture, out float? value)
+ {
+ return ConvertToNullableFloatCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToFloat = ConvertToFloatCore;
+ internal static BindParser ConvertToNullableFloat = ConvertToNullableFloatCore;
+
+ private static bool ConvertToFloatCore(object obj, CultureInfo culture, out float value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ if (float.IsInfinity(converted) || float.IsNaN(converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableFloatCore(object obj, CultureInfo culture, out float? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ if (float.IsInfinity(converted) || float.IsNaN(converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDouble(object obj, CultureInfo culture, out double value)
+ {
+ return ConvertToDoubleCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDouble(object obj, CultureInfo culture, out double? value)
+ {
+ return ConvertToNullableDoubleCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToDoubleDelegate = ConvertToDoubleCore;
+ internal static BindParser ConvertToNullableDoubleDelegate = ConvertToNullableDoubleCore;
+
+ private static bool ConvertToDoubleCore(object obj, CultureInfo culture, out double value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ if (double.IsInfinity(converted) || double.IsNaN(converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableDoubleCore(object obj, CultureInfo culture, out double? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ if (double.IsInfinity(converted) || double.IsNaN(converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDecimal(object obj, CultureInfo culture, out decimal value)
+ {
+ return ConvertToDecimalCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDecimal(object obj, CultureInfo culture, out decimal? value)
+ {
+ return ConvertToNullableDecimalCore(obj, culture, out value);
+ }
+
+ internal static BindParser ConvertToDecimal = ConvertToDecimalCore;
+ internal static BindParser ConvertToNullableDecimal = ConvertToNullableDecimalCore;
+
+ private static bool ConvertToDecimalCore(object obj, CultureInfo culture, out decimal value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableDecimalCore(object obj, CultureInfo culture, out decimal? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDateTime(object obj, CultureInfo culture, out DateTime value)
+ {
+ return ConvertToDateTimeCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The format string to use in conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDateTime(object obj, CultureInfo culture, string format, out DateTime value)
+ {
+ return ConvertToDateTimeCore(obj, culture, format, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDateTime(object obj, CultureInfo culture, out DateTime? value)
+ {
+ return ConvertToNullableDateTimeCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The format string to use in conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDateTime(object obj, CultureInfo culture, string format, out DateTime? value)
+ {
+ return ConvertToNullableDateTimeCore(obj, culture, format, out value);
+ }
+
+ internal static BindParser ConvertToDateTime = ConvertToDateTimeCore;
+ internal static BindParserWithFormat ConvertToDateTimeWithFormat = ConvertToDateTimeCore;
+ internal static BindParser ConvertToNullableDateTime = ConvertToNullableDateTimeCore;
+ internal static BindParserWithFormat ConvertToNullableDateTimeWithFormat = ConvertToNullableDateTimeCore;
+
+ private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, out DateTime value)
+ {
+ return ConvertToDateTimeCore(obj, culture, format: null, out value);
+ }
+
+ private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, string format, out DateTime value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
+ {
+ value = converted;
+ return true;
+ }
+ else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, out DateTime? value)
+ {
+ return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
+ }
+
+ private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, string format, out DateTime? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
+ {
+ value = converted;
+ return true;
+ }
+ else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDateTimeOffset(object obj, CultureInfo culture, out DateTimeOffset value)
+ {
+ return ConvertToDateTimeOffsetCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The format string to use in conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToDateTimeOffset(object obj, CultureInfo culture, string format, out DateTimeOffset value)
+ {
+ return ConvertToDateTimeOffsetCore(obj, culture, format, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDateTimeOffset(object obj, CultureInfo culture, out DateTimeOffset? value)
+ {
+ return ConvertToNullableDateTimeOffsetCore(obj, culture, out value);
+ }
+
+ ///
+ /// Attempts to convert a value to a nullable .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The format string to use in conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertToNullableDateTimeOffset(object obj, CultureInfo culture, string format, out DateTimeOffset? value)
+ {
+ return ConvertToNullableDateTimeOffsetCore(obj, culture, format, out value);
+ }
+
+ internal static BindParser ConvertToDateTimeOffset = ConvertToDateTimeOffsetCore;
+ internal static BindParserWithFormat ConvertToDateTimeOffsetWithFormat = ConvertToDateTimeOffsetCore;
+ internal static BindParser ConvertToNullableDateTimeOffset = ConvertToNullableDateTimeOffsetCore;
+ internal static BindParserWithFormat ConvertToNullableDateTimeOffsetWithFormat = ConvertToNullableDateTimeOffsetCore;
+
+ private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset value)
+ {
+ return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
+ }
+
+ private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return false;
+ }
+
+ if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
+ {
+ value = converted;
+ return true;
+ }
+ else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset? value)
+ {
+ return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
+ }
+
+ private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset? value)
+ {
+ var text = (string)obj;
+ if (string.IsNullOrEmpty(text))
+ {
+ value = default;
+ return true;
+ }
+
+ if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
+ {
+ value = converted;
+ return true;
+ }
+ else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ private static bool ConvertToEnum(object obj, CultureInfo culture, 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;
+ }
+
+ if (!Enum.IsDefined(typeof(T), converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ private static bool ConvertToNullableEnum(object obj, CultureInfo culture, 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;
+ }
+
+ if (!Enum.IsDefined(typeof(T), converted))
+ {
+ value = default;
+ return false;
+ }
+
+ value = converted;
+ return true;
+ }
+
+ ///
+ /// Attempts to convert a value to a value of type .
+ ///
+ /// The object to convert.
+ /// The to use for conversion.
+ /// The converted value.
+ /// true if conversion is successful, otherwise false.
+ public static bool TryConvertTo(object obj, CultureInfo culture, out T value)
+ {
+ var converter = ParserDelegateCache.Get();
+ return converter(obj, culture, out value);
+ }
+
+ private static class FormatterDelegateCache
+ {
+ private readonly static ConcurrentDictionary _cache = new ConcurrentDictionary();
+
+ private static MethodInfo _formatEnumValue;
+ private static MethodInfo _formatNullableEnumValue;
+
+ public static BindFormatter Get()
+ {
+ if (!_cache.TryGetValue(typeof(T), out var formattter))
+ {
+ // 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))
+ {
+ formattter = (BindFormatter)FormatStringValueCore;
+ }
+ else if (typeof(T) == typeof(bool))
+ {
+ formattter = (BindFormatter)FormatBoolValueCore;
+ }
+ else if (typeof(T) == typeof(bool?))
+ {
+ formattter = (BindFormatter)FormatNullableBoolValueCore;
+ }
+ else if (typeof(T) == typeof(int))
+ {
+ formattter = (BindFormatter)FormatIntValueCore;
+ }
+ else if (typeof(T) == typeof(int?))
+ {
+ formattter = (BindFormatter)FormatNullableIntValueCore;
+ }
+ else if (typeof(T) == typeof(long))
+ {
+ formattter = (BindFormatter)FormatLongValueCore;
+ }
+ else if (typeof(T) == typeof(long?))
+ {
+ formattter = (BindFormatter)FormatNullableLongValueCore;
+ }
+ else if (typeof(T) == typeof(float))
+ {
+ formattter = (BindFormatter)FormatFloatValueCore;
+ }
+ else if (typeof(T) == typeof(float?))
+ {
+ formattter = (BindFormatter)FormatNullableFloatValueCore;
+ }
+ else if (typeof(T) == typeof(double))
+ {
+ formattter = (BindFormatter)FormatDoubleValueCore;
+ }
+ else if (typeof(T) == typeof(double?))
+ {
+ formattter = (BindFormatter)FormatNullableDoubleValueCore;
+ }
+ else if (typeof(T) == typeof(decimal))
+ {
+ formattter = (BindFormatter)FormatDecimalValueCore;
+ }
+ else if (typeof(T) == typeof(decimal?))
+ {
+ formattter = (BindFormatter)FormatNullableDecimalValueCore;
+ }
+ else if (typeof(T) == typeof(DateTime))
+ {
+ formattter = (BindFormatter)FormatDateTimeValueCore;
+ }
+ else if (typeof(T) == typeof(DateTime?))
+ {
+ formattter = (BindFormatter)FormatNullableDateTimeValueCore;
+ }
+ else if (typeof(T) == typeof(DateTimeOffset))
+ {
+ formattter = (BindFormatter)FormatDateTimeOffsetValueCore;
+ }
+ else if (typeof(T) == typeof(DateTimeOffset?))
+ {
+ formattter = (BindFormatter)FormatNullableDateTimeOffsetValueCore;
+ }
+ else if (typeof(T).IsEnum)
+ {
+ // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.
+ var method = _formatEnumValue ??= typeof(BindConverter).GetMethod(nameof(FormatEnumValueCore), BindingFlags.NonPublic | BindingFlags.Static);
+ formattter = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindFormatter), 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 = _formatNullableEnumValue ??= typeof(BindConverter).GetMethod(nameof(FormatNullableEnumValueCore), BindingFlags.NonPublic | BindingFlags.Static);
+ formattter = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindFormatter), target: null);
+ }
+ else
+ {
+ formattter = MakeTypeConverterFormatter();
+ }
+
+ _cache.TryAdd(typeof(T), formattter);
+ }
+
+ return (BindFormatter)formattter;
+ }
+
+ private static BindFormatter MakeTypeConverterFormatter()
+ {
+ var typeConverter = TypeDescriptor.GetConverter(typeof(T));
+ if (typeConverter == null || !typeConverter.CanConvertTo(typeof(string)))
+ {
+ throw new InvalidOperationException(
+ $"The type '{typeof(T).FullName}' does not have an associated {typeof(TypeConverter).Name} that supports " +
+ $"conversion to a string. " +
+ $"Apply '{typeof(TypeConverterAttribute).Name}' to the type to register a converter.");
+ }
+
+ return FormatWithTypeConverter;
+
+ string FormatWithTypeConverter(T value, CultureInfo culture)
+ {
+ // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow.
+ return typeConverter.ConvertToString(context: null, culture ?? CultureInfo.CurrentCulture, value);
+ }
+ }
+ }
+
+ internal static class ParserDelegateCache
+ {
+ private readonly static ConcurrentDictionary _cache = new ConcurrentDictionary();
+
+ private static MethodInfo _convertToEnum;
+ private static MethodInfo _convertToNullableEnum;
+
+ public static BindParser Get()
+ {
+ if (!_cache.TryGetValue(typeof(T), out var parser))
+ {
+ // 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))
+ {
+ parser = ConvertToString;
+ }
+ else if (typeof(T) == typeof(bool))
+ {
+ parser = ConvertToBool;
+ }
+ else if (typeof(T) == typeof(bool?))
+ {
+ parser = ConvertToNullableBool;
+ }
+ else if (typeof(T) == typeof(int))
+ {
+ parser = ConvertToInt;
+ }
+ else if (typeof(T) == typeof(int?))
+ {
+ parser = ConvertToNullableInt;
+ }
+ else if (typeof(T) == typeof(long))
+ {
+ parser = ConvertToLong;
+ }
+ else if (typeof(T) == typeof(long?))
+ {
+ parser = ConvertToNullableLong;
+ }
+ else if (typeof(T) == typeof(float))
+ {
+ parser = ConvertToFloat;
+ }
+ else if (typeof(T) == typeof(float?))
+ {
+ parser = ConvertToNullableFloat;
+ }
+ else if (typeof(T) == typeof(double))
+ {
+ parser = ConvertToDoubleDelegate;
+ }
+ else if (typeof(T) == typeof(double?))
+ {
+ parser = ConvertToNullableDoubleDelegate;
+ }
+ else if (typeof(T) == typeof(decimal))
+ {
+ parser = ConvertToDecimal;
+ }
+ else if (typeof(T) == typeof(decimal?))
+ {
+ parser = ConvertToNullableDecimal;
+ }
+ else if (typeof(T) == typeof(DateTime))
+ {
+ parser = ConvertToDateTime;
+ }
+ else if (typeof(T) == typeof(DateTime?))
+ {
+ parser = ConvertToNullableDateTime;
+ }
+ else if (typeof(T) == typeof(DateTimeOffset))
+ {
+ parser = ConvertToDateTime;
+ }
+ else if (typeof(T) == typeof(DateTimeOffset?))
+ {
+ parser = 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(BindConverter).GetMethod(nameof(ConvertToEnum), BindingFlags.NonPublic | BindingFlags.Static);
+ parser = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindParser), 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(BindConverter).GetMethod(nameof(ConvertToNullableEnum), BindingFlags.NonPublic | BindingFlags.Static);
+ parser = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindParser), target: null);
+ }
+ else
+ {
+ parser = MakeTypeConverterConverter();
+ }
+
+ _cache.TryAdd(typeof(T), parser);
+ }
+
+ return (BindParser)parser;
+ }
+
+ private static BindParser 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, CultureInfo culture, out T value)
+ {
+ // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow.
+ var converted = typeConverter.ConvertFrom(context: null, culture ?? CultureInfo.CurrentCulture, obj);
+ if (converted == null)
+ {
+ value = default;
+ return true;
+ }
+
+ value = (T)converted;
+ return true;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Components/Components/src/CascadingValue.cs b/src/Components/Components/src/CascadingValue.cs
index 07a8e79c31..0547d2d38f 100644
--- a/src/Components/Components/src/CascadingValue.cs
+++ b/src/Components/Components/src/CascadingValue.cs
@@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Components
bool ICascadingValueComponent.CurrentValueIsFixed => IsFixed;
///
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}
diff --git a/src/Components/Components/src/ComponentBase.cs b/src/Components/Components/src/ComponentBase.cs
index 045216b722..23dd8734e7 100644
--- a/src/Components/Components/src/ComponentBase.cs
+++ b/src/Components/Components/src/ComponentBase.cs
@@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Components
protected Task InvokeAsync(Func workItem)
=> _renderHandle.Dispatcher.InvokeAsync(workItem);
- void IComponent.Configure(RenderHandle renderHandle)
+ void IComponent.Attach(RenderHandle renderHandle)
{
// This implicitly means a ComponentBase can only be associated with a single
// renderer. That's the only use case we have right now. If there was ever a need,
diff --git a/src/Components/Components/src/ComponentFactory.cs b/src/Components/Components/src/ComponentFactory.cs
index 78f3b7dbfd..aebf96bc06 100644
--- a/src/Components/Components/src/ComponentFactory.cs
+++ b/src/Components/Components/src/ComponentFactory.cs
@@ -1,44 +1,41 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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 Microsoft.AspNetCore.Components.Reflection;
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using Microsoft.AspNetCore.Components.Reflection;
namespace Microsoft.AspNetCore.Components
{
+ ///
+ /// The property on this type is used as a static global cache. Ensure any changes to this type
+ /// are thread safe and can be safely cached statically.
+ ///
internal class ComponentFactory
{
- private readonly static BindingFlags _injectablePropertyBindingFlags
+ private static readonly BindingFlags _injectablePropertyBindingFlags
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
- private readonly IServiceProvider _serviceProvider;
- private readonly IDictionary> _cachedInitializers
- = new ConcurrentDictionary>();
+ private readonly ConcurrentDictionary> _cachedInitializers
+ = new ConcurrentDictionary>();
- public ComponentFactory(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider
- ?? throw new ArgumentNullException(nameof(serviceProvider));
- }
+ public static readonly ComponentFactory Instance = new ComponentFactory();
- public IComponent InstantiateComponent(Type componentType)
+ public IComponent InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
{
- if (!typeof(IComponent).IsAssignableFrom(componentType))
+ var instance = Activator.CreateInstance(componentType);
+ if (!(instance is IComponent component))
{
- throw new ArgumentException($"The type {componentType.FullName} does not " +
- $"implement {nameof(IComponent)}.", nameof(componentType));
+ throw new ArgumentException($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", nameof(componentType));
}
- var instance = (IComponent)Activator.CreateInstance(componentType);
- PerformPropertyInjection(instance);
- return instance;
+ PerformPropertyInjection(serviceProvider, component);
+ return component;
}
- private void PerformPropertyInjection(IComponent instance)
+ private void PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
{
// This is thread-safe because _cachedInitializers is a ConcurrentDictionary.
// We might generate the initializer more than once for a given type, but would
@@ -47,18 +44,19 @@ namespace Microsoft.AspNetCore.Components
if (!_cachedInitializers.TryGetValue(instanceType, out var initializer))
{
initializer = CreateInitializer(instanceType);
- _cachedInitializers[instanceType] = initializer;
+ _cachedInitializers.TryAdd(instanceType, initializer);
}
- initializer(instance);
+ initializer(serviceProvider, instance);
}
- private Action CreateInitializer(Type type)
+ private Action CreateInitializer(Type type)
{
// Do all the reflection up front
var injectableProperties =
MemberAssignment.GetPropertiesIncludingInherited(type, _injectablePropertyBindingFlags)
- .Where(p => p.GetCustomAttribute() != null);
+ .Where(p => p.IsDefined(typeof(InjectAttribute)));
+
var injectables = injectableProperties.Select(property =>
(
propertyName: property.Name,
@@ -66,23 +64,25 @@ namespace Microsoft.AspNetCore.Components
setter: MemberAssignment.CreatePropertySetter(type, property)
)).ToArray();
+ return Initialize;
+
// Return an action whose closure can write all the injected properties
// without any further reflection calls (just typecasts)
- return instance =>
+ void Initialize(IServiceProvider serviceProvider, IComponent component)
{
- foreach (var injectable in injectables)
+ foreach (var (propertyName, propertyType, setter) in injectables)
{
- var serviceInstance = _serviceProvider.GetService(injectable.propertyType);
+ var serviceInstance = serviceProvider.GetService(propertyType);
if (serviceInstance == null)
{
throw new InvalidOperationException($"Cannot provide a value for property " +
- $"'{injectable.propertyName}' on type '{type.FullName}'. There is no " +
- $"registered service of type '{injectable.propertyType}'.");
+ $"'{propertyName}' on type '{type.FullName}'. There is no " +
+ $"registered service of type '{propertyType}'.");
}
- injectable.setter.SetValue(instance, serviceInstance);
+ setter.SetValue(component, serviceInstance);
}
- };
+ }
}
}
}
diff --git a/src/Components/Components/src/ComponentResolver.cs b/src/Components/Components/src/ComponentResolver.cs
deleted file mode 100644
index 4e107da3db..0000000000
--- a/src/Components/Components/src/ComponentResolver.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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 System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-
-namespace Microsoft.AspNetCore.Components
-{
- ///
- /// Resolves components for an application.
- ///
- internal static class ComponentResolver
- {
- ///
- /// Lists all the types
- ///
- ///
- ///
- public static IEnumerable ResolveComponents(Assembly appAssembly)
- {
- var componentsAssembly = typeof(IComponent).Assembly;
-
- return EnumerateAssemblies(appAssembly.GetName(), componentsAssembly, new HashSet(new AssemblyComparer()))
- .SelectMany(a => a.ExportedTypes)
- .Where(t => typeof(IComponent).IsAssignableFrom(t));
- }
-
- private static IEnumerable EnumerateAssemblies(
- AssemblyName assemblyName,
- Assembly componentAssembly,
- HashSet visited)
- {
- var assembly = Assembly.Load(assemblyName);
- if (visited.Contains(assembly))
- {
- // Avoid traversing visited assemblies.
- yield break;
- }
- visited.Add(assembly);
- var references = assembly.GetReferencedAssemblies();
- if (!references.Any(r => string.Equals(r.FullName, componentAssembly.FullName, StringComparison.Ordinal)))
- {
- // Avoid traversing references that don't point to Components (like netstandard2.0)
- yield break;
- }
- else
- {
- yield return assembly;
-
- // Look at the list of transitive dependencies for more components.
- foreach (var reference in references.SelectMany(r => EnumerateAssemblies(r, componentAssembly, visited)))
- {
- yield return reference;
- }
- }
- }
-
- private class AssemblyComparer : IEqualityComparer
- {
- public bool Equals(Assembly x, Assembly y)
- {
- return string.Equals(x?.FullName, y?.FullName, StringComparison.Ordinal);
- }
-
- public int GetHashCode(Assembly obj)
- {
- return obj.FullName.GetHashCode();
- }
- }
- }
-}
diff --git a/src/Components/Components/src/ElementReference.cs b/src/Components/Components/src/ElementReference.cs
new file mode 100644
index 0000000000..92affa1249
--- /dev/null
+++ b/src/Components/Components/src/ElementReference.cs
@@ -0,0 +1,101 @@
+// 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 System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Components
+{
+ ///
+ /// Represents a reference to a rendered element.
+ ///
+ [JsonConverter(typeof(ElementReferenceConverter))]
+ public readonly struct ElementReference
+ {
+ private static long _nextIdForWebAssemblyOnly = 1;
+
+ ///
+ /// Gets a unique identifier for .
+ ///
+ ///
+ /// The Id is unique at least within the scope of a given user/circuit.
+ /// This property is public to support Json serialization and should not be used by user code.
+ ///
+ internal string Id { get; }
+
+ private ElementReference(string id)
+ {
+ Id = id;
+ }
+
+ internal static ElementReference CreateWithUniqueId()
+ => new ElementReference(CreateUniqueId());
+
+ private static string CreateUniqueId()
+ {
+ if (PlatformInfo.IsWebAssembly)
+ {
+ // On WebAssembly there's only one user, so it's fine to expose the number
+ // of IDs that have been assigned, and this is cheaper than creating a GUID.
+ // It's unfortunate that this still involves a heap allocation. If that becomes
+ // a problem we could extend RenderTreeFrame to have both "string" and "long"
+ // fields for ElementRefCaptureId, of which only one would be in use depending
+ // on the platform.
+ var id = Interlocked.Increment(ref _nextIdForWebAssemblyOnly);
+ return id.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ // For remote rendering, it's important not to disclose any cross-user state,
+ // such as the number of IDs that have been assigned.
+ return Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
+ }
+ }
+
+ private sealed class ElementReferenceConverter : JsonConverter
+ {
+ private static readonly JsonEncodedText IdProperty = JsonEncodedText.Encode("__internalId");
+
+ public override ElementReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string id = null;
+ while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
+ {
+ if (reader.TokenType == JsonTokenType.PropertyName)
+ {
+ if (reader.ValueTextEquals(IdProperty.EncodedUtf8Bytes))
+ {
+ reader.Read();
+ id = reader.GetString();
+ }
+ else
+ {
+ throw new JsonException($"Unexpected JSON property '{reader.GetString()}'.");
+ }
+ }
+ else
+ {
+ throw new JsonException($"Unexcepted JSON Token {reader.TokenType}.");
+ }
+ }
+
+ if (id is null)
+ {
+ throw new JsonException("__internalId is required.");
+ }
+
+ return new ElementReference(id);
+ }
+
+ public override void Write(Utf8JsonWriter writer, ElementReference value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(IdProperty, value.Id);
+ writer.WriteEndObject();
+ }
+ }
+ }
+}
diff --git a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs
index 0f8a6d547e..a007c47f09 100644
--- a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs
+++ b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs
@@ -2,10 +2,8 @@
// 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.Globalization;
-using System.Reflection;
+using static Microsoft.AspNetCore.Components.BindConverter;
namespace Microsoft.AspNetCore.Components
{
@@ -24,406 +22,6 @@ namespace Microsoft.AspNetCore.Components
// 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, CultureInfo culture, out T value);
- private delegate bool BindConverterWithFormat(object obj, CultureInfo culture, string format, out T value);
-
- // Perf: conversion delegates are written as static funcs so we can prevent
- // allocations for these simple cases.
- private readonly static BindConverter ConvertToString = ConvertToStringCore;
-
- private static bool ConvertToStringCore(object obj, CultureInfo culture, out string value)
- {
- // We expect the input to already be a string.
- value = (string)obj;
- return true;
- }
-
- private static BindConverter ConvertToBool = ConvertToBoolCore;
- private static BindConverter ConvertToNullableBool = ConvertToNullableBoolCore;
-
- private static bool ConvertToBoolCore(object obj, CultureInfo culture, out bool value)
- {
- // We expect the input to already be a bool.
- value = (bool)obj;
- return true;
- }
-
- private static bool ConvertToNullableBoolCore(object obj, CultureInfo culture, out bool? value)
- {
- // We expect the input to already be a bool.
- value = (bool?)obj;
- return true;
- }
-
- private static BindConverter ConvertToInt = ConvertToIntCore;
- private static BindConverter ConvertToNullableInt = ConvertToNullableIntCore;
-
- private static bool ConvertToIntCore(object obj, CultureInfo culture, out int value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static bool ConvertToNullableIntCore(object obj, CultureInfo culture, out int? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static BindConverter ConvertToLong = ConvertToLongCore;
- private static BindConverter ConvertToNullableLong = ConvertToNullableLongCore;
-
- private static bool ConvertToLongCore(object obj, CultureInfo culture, out long value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static bool ConvertToNullableLongCore(object obj, CultureInfo culture, out long? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static BindConverter ConvertToFloat = ConvertToFloatCore;
- private static BindConverter ConvertToNullableFloat = ConvertToNullableFloatCore;
-
- private static bool ConvertToFloatCore(object obj, CultureInfo culture, out float value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static bool ConvertToNullableFloatCore(object obj, CultureInfo culture, out float? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static BindConverter ConvertToDouble = ConvertToDoubleCore;
- private static BindConverter ConvertToNullableDouble = ConvertToNullableDoubleCore;
-
- private static bool ConvertToDoubleCore(object obj, CultureInfo culture, out double value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static bool ConvertToNullableDoubleCore(object obj, CultureInfo culture, out double? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static BindConverter ConvertToDecimal = ConvertToDecimalCore;
- private static BindConverter ConvertToNullableDecimal = ConvertToNullableDecimalCore;
-
- private static bool ConvertToDecimalCore(object obj, CultureInfo culture, out decimal value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static bool ConvertToNullableDecimalCore(object obj, CultureInfo culture, out decimal? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
- {
- value = default;
- return false;
- }
-
- value = converted;
- return true;
- }
-
- private static BindConverter ConvertToDateTime = ConvertToDateTimeCore;
- private static BindConverterWithFormat ConvertToDateTimeWithFormat = ConvertToDateTimeCore;
- private static BindConverter ConvertToNullableDateTime = ConvertToNullableDateTimeCore;
- private static BindConverterWithFormat ConvertToNullableDateTimeWithFormat = ConvertToNullableDateTimeCore;
-
- private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, out DateTime value)
- {
- return ConvertToDateTimeCore(obj, culture, format: null, out value);
- }
-
- private static bool ConvertToDateTimeCore(object obj, CultureInfo culture, string format, out DateTime value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
- {
- value = converted;
- return true;
- }
- else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
- {
- value = converted;
- return true;
- }
-
- value = default;
- return false;
- }
-
- private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, out DateTime? value)
- {
- return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
- }
-
- private static bool ConvertToNullableDateTimeCore(object obj, CultureInfo culture, string format, out DateTime? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
- {
- value = converted;
- return true;
- }
- else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
- {
- value = converted;
- return true;
- }
-
- value = default;
- return false;
- }
-
- private static BindConverter ConvertToDateTimeOffset = ConvertToDateTimeOffsetCore;
- private static BindConverterWithFormat ConvertToDateTimeOffsetWithFormat = ConvertToDateTimeOffsetCore;
- private static BindConverter ConvertToNullableDateTimeOffset = ConvertToNullableDateTimeOffsetCore;
- private static BindConverterWithFormat ConvertToNullableDateTimeOffsetWithFormat = ConvertToNullableDateTimeOffsetCore;
-
- private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset value)
- {
- return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
- }
-
- private static bool ConvertToDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return false;
- }
-
- if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
- {
- value = converted;
- return true;
- }
- else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
- {
- value = converted;
- return true;
- }
-
- value = default;
- return false;
- }
-
- private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, out DateTimeOffset? value)
- {
- return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
- }
-
- private static bool ConvertToNullableDateTimeOffsetCore(object obj, CultureInfo culture, string format, out DateTimeOffset? value)
- {
- var text = (string)obj;
- if (string.IsNullOrEmpty(text))
- {
- value = default;
- return true;
- }
-
- if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
- {
- value = converted;
- return true;
- }
- else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
- {
- value = converted;
- return true;
- }
-
- value = default;
- return false;
- }
-
- private static bool ConvertToEnum(object obj, CultureInfo culture, 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, CultureInfo culture, 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;
- }
-
///
/// For internal use only.
///
@@ -611,7 +209,7 @@ namespace Microsoft.AspNetCore.Components
double existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, ConvertToDouble);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToDoubleDelegate);
}
///
@@ -630,7 +228,7 @@ namespace Microsoft.AspNetCore.Components
double? existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDouble);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDoubleDelegate);
}
///
@@ -687,7 +285,7 @@ namespace Microsoft.AspNetCore.Components
DateTime existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, format: null, ConvertToDateTimeWithFormat);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTime);
}
///
@@ -727,7 +325,7 @@ namespace Microsoft.AspNetCore.Components
DateTime? existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeWithFormat);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTime);
}
///
@@ -767,7 +365,7 @@ namespace Microsoft.AspNetCore.Components
DateTimeOffset existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, format: null, ConvertToDateTimeOffsetWithFormat);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTimeOffset);
}
///
@@ -807,7 +405,7 @@ namespace Microsoft.AspNetCore.Components
DateTimeOffset? existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, format: null, ConvertToNullableDateTimeOffsetWithFormat);
+ return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTimeOffset);
}
///
@@ -848,7 +446,7 @@ namespace Microsoft.AspNetCore.Components
T existingValue,
CultureInfo culture = null)
{
- return CreateBinderCore(factory, receiver, setter, culture, BinderConverterCache.Get());
+ return CreateBinderCore(factory, receiver, setter, culture, ParserDelegateCache.Get());
}
private static EventCallback CreateBinderCore(
@@ -856,7 +454,7 @@ namespace Microsoft.AspNetCore.Components
object receiver,
Action setter,
CultureInfo culture,
- BindConverter converter)
+ BindConverter.BindParser converter)
{
Action callback = e =>
{
@@ -900,7 +498,7 @@ namespace Microsoft.AspNetCore.Components
Action setter,
CultureInfo culture,
string format,
- BindConverterWithFormat converter)
+ BindConverter.BindParserWithFormat converter)
{
Action callback = e =>
{
@@ -937,147 +535,5 @@ 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) == typeof(DateTimeOffset))
- {
- converter = ConvertToDateTimeOffset;
- }
- else if (typeof(T) == typeof(DateTimeOffset?))
- {
- converter = ConvertToNullableDateTimeOffset;
- }
- 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, CultureInfo culture, 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, culture ?? CultureInfo.CurrentCulture, text);
- if (converted == null)
- {
- value = default;
- return false;
- }
-
- value = (T)converted;
- return true;
- }
- }
- }
}
}
diff --git a/src/Components/Components/src/Forms/InputComponents/InputCheckbox.cs b/src/Components/Components/src/Forms/InputComponents/InputCheckbox.cs
index df84c8bdaa..981287ee8d 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputCheckbox.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputCheckbox.cs
@@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Forms
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "type", "checkbox");
builder.AddAttribute(3, "class", CssClass);
- builder.AddAttribute(4, "checked", BindMethods.GetValue(CurrentValue));
+ builder.AddAttribute(4, "checked", BindConverter.FormatValue(CurrentValue));
builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValue = __value, CurrentValue));
builder.CloseElement();
}
diff --git a/src/Components/Components/src/Forms/InputComponents/InputDate.cs b/src/Components/Components/src/Forms/InputComponents/InputDate.cs
index 0a79bbc15d..f080af908d 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputDate.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputDate.cs
@@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.Forms
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "type", "date");
builder.AddAttribute(3, "class", CssClass);
- builder.AddAttribute(4, "value", BindMethods.GetValue(CurrentValueAsString));
+ builder.AddAttribute(4, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.CloseElement();
}
@@ -38,9 +38,9 @@ namespace Microsoft.AspNetCore.Components.Forms
switch (value)
{
case DateTime dateTimeValue:
- return dateTimeValue.ToString(DateFormat, CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(dateTimeValue, DateFormat, CultureInfo.InvariantCulture);
case DateTimeOffset dateTimeOffsetValue:
- return dateTimeOffsetValue.ToString(DateFormat, CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(dateTimeOffsetValue, DateFormat, CultureInfo.InvariantCulture);
default:
return string.Empty; // Handles null for Nullable, etc.
}
@@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Components.Forms
static bool TryParseDateTime(string value, out T result)
{
- var success = DateTime.TryParseExact(value, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedValue);
+ var success = BindConverter.TryConvertToDateTime(value, CultureInfo.InvariantCulture, DateFormat, out var parsedValue);
if (success)
{
result = (T)(object)parsedValue;
@@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.Forms
static bool TryParseDateTimeOffset(string value, out T result)
{
- var success = DateTimeOffset.TryParseExact(value, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedValue);
+ var success = BindConverter.TryConvertToDateTimeOffset(value, CultureInfo.InvariantCulture, DateFormat, out var parsedValue);
if (success)
{
result = (T)(object)parsedValue;
diff --git a/src/Components/Components/src/Forms/InputComponents/InputNumber.cs b/src/Components/Components/src/Forms/InputComponents/InputNumber.cs
index 044f4aac3d..c5a222f6ff 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputNumber.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputNumber.cs
@@ -13,38 +13,18 @@ namespace Microsoft.AspNetCore.Components.Forms
///
public class InputNumber : InputBase
{
- delegate bool Parser(string value, out T result);
- private static Parser _parser;
private static string _stepAttributeValue; // Null by default, so only allows whole numbers as per HTML spec
- // Determine the parsing logic once per T and cache it, so we don't have to consider all the possible types on each parse
static InputNumber()
{
// Unwrap Nullable, because InputBase already deals with the Nullable aspect
// of it for us. We will only get asked to parse the T for nonempty inputs.
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
-
- if (targetType == typeof(int))
+ if (targetType == typeof(int) ||
+ targetType == typeof(float) ||
+ targetType == typeof(double) ||
+ targetType == typeof(decimal))
{
- _parser = TryParseInt;
- }
- else if (targetType == typeof(long))
- {
- _parser = TryParseLong;
- }
- else if (targetType == typeof(float))
- {
- _parser = TryParseFloat;
- _stepAttributeValue = "any";
- }
- else if (targetType == typeof(double))
- {
- _parser = TryParseDouble;
- _stepAttributeValue = "any";
- }
- else if (targetType == typeof(decimal))
- {
- _parser = TryParseDecimal;
_stepAttributeValue = "any";
}
else
@@ -62,11 +42,11 @@ namespace Microsoft.AspNetCore.Components.Forms
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "input");
- builder.AddAttribute(1, "step", _stepAttributeValue); // Before the splat so the user can override
+ builder.AddAttribute(1, "step", _stepAttributeValue);
builder.AddMultipleAttributes(2, AdditionalAttributes);
builder.AddAttribute(3, "type", "number");
builder.AddAttribute(4, "class", CssClass);
- builder.AddAttribute(5, "value", BindMethods.GetValue(CurrentValueAsString));
+ builder.AddAttribute(5, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(6, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.CloseElement();
}
@@ -74,7 +54,7 @@ namespace Microsoft.AspNetCore.Components.Forms
///
protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage)
{
- if (_parser(value, out result))
+ if (BindConverter.TryConvertTo(value, CultureInfo.InvariantCulture, out result))
{
validationErrorMessage = null;
return true;
@@ -100,98 +80,23 @@ namespace Microsoft.AspNetCore.Components.Forms
return null;
case int @int:
- return @int.ToString(CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(@int, CultureInfo.InvariantCulture);
case long @long:
- return @long.ToString(CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(@long, CultureInfo.InvariantCulture);
case float @float:
- return @float.ToString(CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(@float, CultureInfo.InvariantCulture);
case double @double:
- return @double.ToString(CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(@double, CultureInfo.InvariantCulture);
case decimal @decimal:
- return @decimal.ToString(CultureInfo.InvariantCulture);
+ return BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture);
default:
throw new InvalidOperationException($"Unsupported type {value.GetType()}");
}
}
-
- static bool TryParseInt(string value, out T result)
- {
- var success = int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue);
- if (success)
- {
- result = (T)(object)parsedValue;
- return true;
- }
- else
- {
- result = default;
- return false;
- }
- }
-
- static bool TryParseLong(string value, out T result)
- {
- var success = long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue);
- if (success)
- {
- result = (T)(object)parsedValue;
- return true;
- }
- else
- {
- result = default;
- return false;
- }
- }
-
- static bool TryParseFloat(string value, out T result)
- {
- var success = float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedValue);
- if (success && !float.IsInfinity(parsedValue))
- {
- result = (T)(object)parsedValue;
- return true;
- }
- else
- {
- result = default;
- return false;
- }
- }
-
- static bool TryParseDouble(string value, out T result)
- {
- var success = double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedValue);
- if (success && !double.IsInfinity(parsedValue))
- {
- result = (T)(object)parsedValue;
- return true;
- }
- else
- {
- result = default;
- return false;
- }
- }
-
- static bool TryParseDecimal(string value, out T result)
- {
- var success = decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedValue);
- if (success)
- {
- result = (T)(object)parsedValue;
- return true;
- }
- else
- {
- result = default;
- return false;
- }
- }
}
}
diff --git a/src/Components/Components/src/Forms/InputComponents/InputSelect.cs b/src/Components/Components/src/Forms/InputComponents/InputSelect.cs
index 6705ce9320..fda1822afe 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputSelect.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputSelect.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Globalization;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Microsoft.AspNetCore.Components.Forms
@@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.Forms
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
- builder.AddAttribute(3, "value", BindMethods.GetValue(CurrentValueAsString));
+ builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.AddContent(5, ChildContent);
builder.CloseElement();
@@ -39,14 +40,14 @@ namespace Microsoft.AspNetCore.Components.Forms
}
else if (typeof(T).IsEnum)
{
- // There's no non-generic Enum.TryParse (https://github.com/dotnet/corefx/issues/692)
- try
+ var success = BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out var parsedValue);
+ if (success)
{
- result = (T)Enum.Parse(typeof(T), value);
+ result = parsedValue;
validationErrorMessage = null;
return true;
}
- catch (ArgumentException)
+ else
{
result = default;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
diff --git a/src/Components/Components/src/Forms/InputComponents/InputText.cs b/src/Components/Components/src/Forms/InputComponents/InputText.cs
index ffb7004601..94c09c4694 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputText.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputText.cs
@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.Forms
builder.OpenElement(0, "input");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
- builder.AddAttribute(3, "value", BindMethods.GetValue(CurrentValue));
+ builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.CloseElement();
}
diff --git a/src/Components/Components/src/Forms/InputComponents/InputTextArea.cs b/src/Components/Components/src/Forms/InputComponents/InputTextArea.cs
index ed676d6404..ba61d95896 100644
--- a/src/Components/Components/src/Forms/InputComponents/InputTextArea.cs
+++ b/src/Components/Components/src/Forms/InputComponents/InputTextArea.cs
@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.Forms
builder.OpenElement(0, "textarea");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
- builder.AddAttribute(3, "value", BindMethods.GetValue(CurrentValue));
+ builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.CloseElement();
}
diff --git a/src/Components/Components/src/IComponent.cs b/src/Components/Components/src/IComponent.cs
index d775efdbd9..c06dbb193e 100644
--- a/src/Components/Components/src/IComponent.cs
+++ b/src/Components/Components/src/IComponent.cs
@@ -11,10 +11,10 @@ namespace Microsoft.AspNetCore.Components
public interface IComponent
{
///
- /// Initializes the component.
+ /// Attaches the component to a .
///
/// A that allows the component to be rendered.
- void Configure(RenderHandle renderHandle);
+ void Attach(RenderHandle renderHandle);
///
/// Sets parameters supplied by the component's parent in the render tree.
diff --git a/src/Components/Components/src/LayoutComponentBase.cs b/src/Components/Components/src/LayoutComponentBase.cs
index b9c8672d86..2480cd2e18 100644
--- a/src/Components/Components/src/LayoutComponentBase.cs
+++ b/src/Components/Components/src/LayoutComponentBase.cs
@@ -16,6 +16,6 @@ namespace Microsoft.AspNetCore.Components
/// Gets the content to be rendered inside the layout.
///
[Parameter]
- protected RenderFragment Body { get; private set; }
+ public RenderFragment Body { get; private set; }
}
}
diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
index 4a6ddef33d..6c773e9d42 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -9,7 +9,7 @@
3.0
-
+
@@ -19,4 +19,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Microsoft.AspNetCore.Components.nuspec
+ $(GenerateNuspecDependsOn);_GetNuspecDependencyPackageVersions
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.nuspec
new file mode 100644
index 0000000000..415e911bfe
--- /dev/null
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.nuspec
@@ -0,0 +1,20 @@
+
+
+
+ $CommonMetadataElements$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/Components/src/PageDisplay.cs b/src/Components/Components/src/PageDisplay.cs
index d109f51557..37e429915b 100644
--- a/src/Components/Components/src/PageDisplay.cs
+++ b/src/Components/Components/src/PageDisplay.cs
@@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Components
public RenderFragment AuthorizingContent { get; private set; }
///
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}
diff --git a/src/Components/Components/src/RenderTree/ArrayBuilder.cs b/src/Components/Components/src/RenderTree/ArrayBuilder.cs
index 6be35ded27..9d3e71993a 100644
--- a/src/Components/Components/src/RenderTree/ArrayBuilder.cs
+++ b/src/Components/Components/src/RenderTree/ArrayBuilder.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.Buffers;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Components.RenderTree
@@ -15,26 +17,25 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// components can be long-lived and re-render frequently, with the rendered size
/// varying dramatically depending on the user's navigation in the app.
///
- internal class ArrayBuilder
+ internal class ArrayBuilder : IDisposable
{
- private const int MinCapacity = 10;
+ // The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties.
private T[] _items;
private int _itemsInUse;
- ///
- /// Constructs a new instance of .
- ///
- public ArrayBuilder() : this(MinCapacity)
- {
- }
+ private static readonly T[] Empty = Array.Empty();
+ private readonly ArrayPool _arrayPool;
+ private readonly int _minCapacity;
+ private bool _disposed;
///
/// Constructs a new instance of .
///
- public ArrayBuilder(int capacity)
+ public ArrayBuilder(int minCapacity = 32, ArrayPool arrayPool = null)
{
- _items = new T[capacity < MinCapacity ? MinCapacity : capacity];
- _itemsInUse = 0;
+ _arrayPool = arrayPool ?? ArrayPool.Shared;
+ _minCapacity = minCapacity;
+ _items = Empty;
}
///
@@ -57,7 +58,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
if (_itemsInUse == _items.Length)
{
- SetCapacity(_items.Length * 2, preserveContents: true);
+ GrowBuffer(_items.Length * 2);
}
var indexOfAppendedItem = _itemsInUse++;
@@ -72,13 +73,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var requiredCapacity = _itemsInUse + length;
if (_items.Length < requiredCapacity)
{
- var candidateCapacity = _items.Length * 2;
+ var candidateCapacity = Math.Max(_items.Length * 2, _minCapacity);
while (candidateCapacity < requiredCapacity)
{
candidateCapacity *= 2;
}
- SetCapacity(candidateCapacity, preserveContents: true);
+ GrowBuffer(candidateCapacity);
}
Array.Copy(source, startIndex, _items, _itemsInUse, length);
@@ -95,34 +96,51 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// The value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Overwrite(int index, in T value)
- => _items[index] = value;
+ {
+ if (index > _itemsInUse)
+ {
+ ThrowIndexOutOfBoundsException();
+ }
+
+ _items[index] = value;
+ }
///
/// Removes the last item.
///
public void RemoveLast()
{
+ if (_itemsInUse == 0)
+ {
+ ThrowIndexOutOfBoundsException();
+ }
+
_itemsInUse--;
- _items[_itemsInUse] = default(T); // Release to GC
+ _items[_itemsInUse] = default; // Release to GC
}
///
/// Inserts the item at the specified index, moving the contents of the subsequent entries along by one.
///
- /// The index at which the value is to be inserted.
+ /// The index at which the value is to be inserted.
/// The value to insert.
- public void InsertExpensive(int insertAtIndex, T value)
+ public void InsertExpensive(int index, T value)
{
+ if (index > _itemsInUse)
+ {
+ ThrowIndexOutOfBoundsException();
+ }
+
// Same expansion logic as elsewhere
if (_itemsInUse == _items.Length)
{
- SetCapacity(_items.Length * 2, preserveContents: true);
+ GrowBuffer(_items.Length * 2);
}
- Array.Copy(_items, insertAtIndex, _items, insertAtIndex + 1, _itemsInUse - insertAtIndex);
+ Array.Copy(_items, index, _items, index + 1, _itemsInUse - index);
_itemsInUse++;
- _items[insertAtIndex] = value;
+ _items[index] = value;
}
///
@@ -131,17 +149,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
///
public void Clear()
{
- var previousItemsInUse = _itemsInUse;
+ ReturnBuffer();
+ _items = Empty;
_itemsInUse = 0;
-
- if (_items.Length > previousItemsInUse * 1.5)
- {
- SetCapacity((previousItemsInUse + _items.Length) / 2, preserveContents: false);
- }
- else if (previousItemsInUse > 0)
- {
- Array.Clear(_items, 0, previousItemsInUse); // Release to GC
- }
}
///
@@ -160,29 +170,42 @@ namespace Microsoft.AspNetCore.Components.RenderTree
public ArrayBuilderSegment ToSegment(int fromIndexInclusive, int toIndexExclusive)
=> new ArrayBuilderSegment(this, fromIndexInclusive, toIndexExclusive - fromIndexInclusive);
- private void SetCapacity(int desiredCapacity, bool preserveContents)
+ private void GrowBuffer(int desiredCapacity)
{
- if (desiredCapacity < _itemsInUse)
- {
- throw new ArgumentOutOfRangeException(nameof(desiredCapacity), $"The value cannot be less than {nameof(Count)}");
- }
+ var newCapacity = Math.Max(desiredCapacity, _minCapacity);
+ Debug.Assert(newCapacity > _items.Length);
- var newCapacity = desiredCapacity < MinCapacity ? MinCapacity : desiredCapacity;
- if (newCapacity != _items.Length)
- {
- var newItems = new T[newCapacity];
+ var newItems = _arrayPool.Rent(newCapacity);
+ Array.Copy(_items, newItems, _itemsInUse);
- if (preserveContents)
- {
- Array.Copy(_items, newItems, _itemsInUse);
- }
+ // Return the old buffer and start using the new buffer
+ ReturnBuffer();
+ _items = newItems;
+ }
- _items = newItems;
- }
- else if (!preserveContents)
+ private void ReturnBuffer()
+ {
+ if (!ReferenceEquals(_items, Empty))
{
- Array.Clear(_items, 0, _items.Length);
+ // ArrayPool<>.Return with clearArray: true calls Array.Clear on the entire buffer.
+ // In the most common case, _itemsInUse would be much smaller than _items.Length so we'll specifically clear that subset.
+ Array.Clear(_items, 0, _itemsInUse);
+ _arrayPool.Return(_items);
}
}
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ ReturnBuffer();
+ }
+ }
+
+ private static void ThrowIndexOutOfBoundsException()
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
}
}
diff --git a/src/Components/Components/src/RenderTree/ArrayBuilderSegment.cs b/src/Components/Components/src/RenderTree/ArrayBuilderSegment.cs
index a868b654cc..18317ece48 100644
--- a/src/Components/Components/src/RenderTree/ArrayBuilderSegment.cs
+++ b/src/Components/Components/src/RenderTree/ArrayBuilderSegment.cs
@@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// The type of the elements in the array
public readonly struct ArrayBuilderSegment : IEnumerable
{
+ // The following fields are memory mapped to the WASM client. Do not re-order or use auto-properties.
private readonly ArrayBuilder _builder;
private readonly int _offset;
private readonly int _count;
diff --git a/src/Components/Components/src/RenderTree/RenderTreeBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeBuilder.cs
index 3b48edd123..b0e0687617 100644
--- a/src/Components/Components/src/RenderTree/RenderTreeBuilder.cs
+++ b/src/Components/Components/src/RenderTree/RenderTreeBuilder.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.RenderTree
@@ -18,14 +17,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
///
/// Provides methods for building a collection of entries.
///
- public class RenderTreeBuilder
+ public class RenderTreeBuilder : IDisposable
{
private readonly static object BoxedTrue = true;
private readonly static object BoxedFalse = false;
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
private readonly Renderer _renderer;
- private readonly ArrayBuilder _entries = new ArrayBuilder(10);
+ private readonly ArrayBuilder _entries = new ArrayBuilder();
private readonly Stack _openElementIndices = new Stack();
private RenderTreeFrameType? _lastNonAttributeFrameType;
private bool _hasSeenAddMultipleAttributes;
@@ -524,7 +523,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
{
if (value == null)
{
- throw new ArgumentNullException(nameof(value));
+ // Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an
+ // easy way to have conditional directive attributes
+ return;
}
var parentFrameIndex = GetCurrentParentFrameIndex();
@@ -795,5 +796,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary(StringComparer.OrdinalIgnoreCase));
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
}
+
+ void IDisposable.Dispose()
+ {
+ _entries.Dispose();
+ }
}
}
diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
index e11b12bb24..933e1f9ab5 100644
--- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
+++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
@@ -50,6 +50,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// if you plan to refactor this, be sure to benchmark the old and new versions
// on Mono WebAssembly.
+ var origOldStartIndex = oldStartIndex;
+ var origNewStartIndex = newStartIndex;
var hasMoreOld = oldEndIndexExcl > oldStartIndex;
var hasMoreNew = newEndIndexExcl > newStartIndex;
var prevOldSeq = -1;
@@ -108,13 +110,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// Keys don't match
if (keyedItemInfos == null)
{
- keyedItemInfos = BuildKeyToInfoLookup(diffContext, oldStartIndex, oldEndIndexExcl, newStartIndex, newEndIndexExcl);
+ keyedItemInfos = BuildKeyToInfoLookup(diffContext, origOldStartIndex, oldEndIndexExcl, origNewStartIndex, newEndIndexExcl);
}
- var oldKeyItemInfo = oldKey != null ? keyedItemInfos[oldKey] : new KeyedItemInfo(-1, -1, false);
- var newKeyItemInfo = newKey != null ? keyedItemInfos[newKey] : new KeyedItemInfo(-1, -1, false);
- var oldKeyIsInNewTree = oldKeyItemInfo.NewIndex >= 0 && oldKeyItemInfo.IsUnique;
- var newKeyIsInOldTree = newKeyItemInfo.OldIndex >= 0 && newKeyItemInfo.IsUnique;
+ var oldKeyItemInfo = oldKey != null ? keyedItemInfos[oldKey] : new KeyedItemInfo(-1, -1);
+ var newKeyItemInfo = newKey != null ? keyedItemInfos[newKey] : new KeyedItemInfo(-1, -1);
+ var oldKeyIsInNewTree = oldKeyItemInfo.NewIndex >= 0;
+ var newKeyIsInOldTree = newKeyItemInfo.OldIndex >= 0;
// If either key is not in the other tree, we can handle it as an insert or a delete
// on this iteration. We're only forced to use the move logic that's not the case
@@ -304,7 +306,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var key = KeyValue(ref frame);
if (key != null)
{
- result[key] = new KeyedItemInfo(oldStartIndex, -1, isUnique: !result.ContainsKey(key));
+ if (result.ContainsKey(key))
+ {
+ ThrowExceptionForDuplicateKey(key);
+ }
+
+ result[key] = new KeyedItemInfo(oldStartIndex, -1);
}
oldStartIndex = NextSiblingIndex(frame, oldStartIndex);
@@ -316,9 +323,19 @@ namespace Microsoft.AspNetCore.Components.RenderTree
var key = KeyValue(ref frame);
if (key != null)
{
- result[key] = result.TryGetValue(key, out var existingEntry)
- ? new KeyedItemInfo(existingEntry.OldIndex, newStartIndex, isUnique: existingEntry.NewIndex < 0)
- : new KeyedItemInfo(-1, newStartIndex, isUnique: true);
+ if (!result.TryGetValue(key, out var existingEntry))
+ {
+ result[key] = new KeyedItemInfo(-1, newStartIndex);
+ }
+ else
+ {
+ if (existingEntry.NewIndex >= 0)
+ {
+ ThrowExceptionForDuplicateKey(key);
+ }
+
+ result[key] = new KeyedItemInfo(existingEntry.OldIndex, newStartIndex);
+ }
}
newStartIndex = NextSiblingIndex(frame, newStartIndex);
@@ -327,6 +344,11 @@ namespace Microsoft.AspNetCore.Components.RenderTree
return result;
}
+ private static void ThrowExceptionForDuplicateKey(object key)
+ {
+ throw new InvalidOperationException($"More than one sibling has the same key value, '{key}'. Key values must be unique.");
+ }
+
private static object KeyValue(ref RenderTreeFrame frame)
{
switch (frame.FrameType)
diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrame.cs b/src/Components/Components/src/RenderTree/RenderTreeFrame.cs
index 0226cfd9d7..1c0105ad77 100644
--- a/src/Components/Components/src/RenderTree/RenderTreeFrame.cs
+++ b/src/Components/Components/src/RenderTree/RenderTreeFrame.cs
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
///
/// Represents an entry in a tree of user interface (UI) items.
///
- [StructLayout(LayoutKind.Explicit)]
+ [StructLayout(LayoutKind.Explicit, Pack = 4)]
public readonly struct RenderTreeFrame
{
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
@@ -24,8 +24,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
// Offset Type
// ------ ----
// 0-3 Int32 (sequence number)
- // 4-7 Int32 (frame type)
- // 8-15 Value types (usage varies by frame type)
+ // 4-5 Int16 (frame type)
+ // 6-15 Value types (usage varies by frame type)
// 16-23 Reference type (usage varies by frame type)
// 24-31 Reference type (usage varies by frame type)
// 32-39 Reference type (usage varies by frame type)
@@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// If the property equals
/// gets the ID of the corresponding event handler, if any.
///
- [FieldOffset(8)] public readonly int AttributeEventHandlerId;
+ [FieldOffset(8)] public readonly ulong AttributeEventHandlerId;
///
/// If the property equals ,
@@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
}
// Attribute constructor
- private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int attributeEventHandlerId, string attributeEventUpdatesAttributeName)
+ private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName)
: this()
{
FrameType = RenderTreeFrameType.Attribute;
@@ -337,7 +337,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
internal RenderTreeFrame WithComponent(ComponentState componentState)
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey);
- internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
+ internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId)
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName);
internal RenderTreeFrame WithAttributeValue(object attributeValue)
diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs
index 09630e3fcf..61d2558305 100644
--- a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs
+++ b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs
@@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
///
/// Describes the type of a .
///
- public enum RenderTreeFrameType: int
+ public enum RenderTreeFrameType: short
{
///
/// Used only for unintialized frames.
diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs
index bc9b7e3277..1d34ffff84 100644
--- a/src/Components/Components/src/Rendering/ComponentState.cs
+++ b/src/Components/Components/src/Rendering/ComponentState.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// within the context of a . This is an internal implementation
/// detail of .
///
- internal class ComponentState
+ internal class ComponentState : IDisposable
{
private readonly Renderer _renderer;
private readonly IReadOnlyList _cascadingParameters;
@@ -91,6 +91,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
RemoveCascadingParameterSubscriptions();
}
+
+ DisposeBuffers();
}
public Task NotifyRenderCompletedAsync()
@@ -172,5 +174,22 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
}
}
+
+ public void Dispose()
+ {
+ DisposeBuffers();
+
+ if (Component is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ }
+
+ private void DisposeBuffers()
+ {
+ ((IDisposable)_renderTreeBuilderPrevious).Dispose();
+ ((IDisposable)CurrrentRenderTree).Dispose();
+ _latestDirectParametersSnapshot?.Dispose();
+ }
}
}
diff --git a/src/Components/Components/src/Rendering/KeyedItemInfo.cs b/src/Components/Components/src/Rendering/KeyedItemInfo.cs
index f2a56341e2..c8a7de5e1a 100644
--- a/src/Components/Components/src/Rendering/KeyedItemInfo.cs
+++ b/src/Components/Components/src/Rendering/KeyedItemInfo.cs
@@ -10,24 +10,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
public readonly int NewIndex;
public readonly int OldSiblingIndex;
public readonly int NewSiblingIndex;
- public readonly bool IsUnique;
- public KeyedItemInfo(int oldIndex, int newIndex, bool isUnique)
+ public KeyedItemInfo(int oldIndex, int newIndex)
{
OldIndex = oldIndex;
NewIndex = newIndex;
OldSiblingIndex = -1;
NewSiblingIndex = -1;
-
- // Non-unique keys are problematic, because there's no way to know which instance
- // should match with which other, plus they would force us to keep track of which
- // usages have been consumed as we proceed through the diff. Since this is such
- // an edge case, we "tolerate" it just by tracking which keys have duplicates, and
- // for those ones, we never treat them as moved. Instead for those we fall back on
- // insert+delete behavior, i.e., not preserving elements/components.
- //
- // Guidance for developers is therefore to use distinct keys.
- IsUnique = isUnique;
}
private KeyedItemInfo(in KeyedItemInfo copyFrom, int oldSiblingIndex, int newSiblingIndex)
diff --git a/src/Components/Components/src/Rendering/RenderBatch.cs b/src/Components/Components/src/Rendering/RenderBatch.cs
index cb79c0ff0d..e4e0d6cb4f 100644
--- a/src/Components/Components/src/Rendering/RenderBatch.cs
+++ b/src/Components/Components/src/Rendering/RenderBatch.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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 Microsoft.AspNetCore.Components.RenderTree;
@@ -30,13 +30,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
///
/// Gets the IDs of the event handlers that were disposed.
///
- public ArrayRange DisposedEventHandlerIDs { get; }
+ public ArrayRange DisposedEventHandlerIDs { get; }
internal RenderBatch(
ArrayRange updatedComponents,
ArrayRange referenceFrames,
ArrayRange disposedComponentIDs,
- ArrayRange disposedEventHandlerIDs)
+ ArrayRange disposedEventHandlerIDs)
{
UpdatedComponents = updatedComponents;
ReferenceFrames = referenceFrames;
diff --git a/src/Components/Components/src/Rendering/RenderBatchBuilder.cs b/src/Components/Components/src/Rendering/RenderBatchBuilder.cs
index bb5daacec4..f26a248fd8 100644
--- a/src/Components/Components/src/Rendering/RenderBatchBuilder.cs
+++ b/src/Components/Components/src/Rendering/RenderBatchBuilder.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 System.Collections.Generic;
using Microsoft.AspNetCore.Components.RenderTree;
@@ -12,16 +13,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// and the intermediate states (such as the queue of components still to
/// be rendered).
///
- internal class RenderBatchBuilder
+ internal class RenderBatchBuilder : IDisposable
{
// Primary result data
public ArrayBuilder UpdatedComponentDiffs { get; } = new ArrayBuilder();
public ArrayBuilder DisposedComponentIds { get; } = new ArrayBuilder();
- public ArrayBuilder DisposedEventHandlerIds { get; } = new ArrayBuilder();
+ public ArrayBuilder DisposedEventHandlerIds { get; } = new ArrayBuilder();
// Buffers referenced by UpdatedComponentDiffs
- public ArrayBuilder EditsBuffer { get; } = new ArrayBuilder();
- public ArrayBuilder ReferenceFramesBuffer { get; } = new ArrayBuilder();
+ public ArrayBuilder EditsBuffer { get; } = new ArrayBuilder(64);
+ public ArrayBuilder ReferenceFramesBuffer { get; } = new ArrayBuilder(64);
// State of render pipeline
public Queue ComponentRenderQueue { get; } = new Queue();
@@ -56,5 +57,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
ReferenceFramesBuffer.ToRange(),
DisposedComponentIds.ToRange(),
DisposedEventHandlerIds.ToRange());
+
+ public void Dispose()
+ {
+ EditsBuffer.Dispose();
+ ReferenceFramesBuffer.Dispose();
+ UpdatedComponentDiffs.Dispose();
+ DisposedComponentIds.Dispose();
+ DisposedEventHandlerIds.Dispose();
+ }
}
}
diff --git a/src/Components/Components/src/Rendering/RenderTreeUpdater.cs b/src/Components/Components/src/Rendering/RenderTreeUpdater.cs
index 5168ba3936..139b3e09d7 100644
--- a/src/Components/Components/src/Rendering/RenderTreeUpdater.cs
+++ b/src/Components/Components/src/Rendering/RenderTreeUpdater.cs
@@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
internal class RenderTreeUpdater
{
- public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, int eventHandlerId, object newFieldValue)
+ public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, ulong eventHandlerId, object newFieldValue)
{
// We only allow the client to supply string or bool currently, since those are the only kinds of
// values we output on attributes that go to the client
diff --git a/src/Components/Components/src/Rendering/Renderer.Log.cs b/src/Components/Components/src/Rendering/Renderer.Log.cs
index 1a737c21c4..68a28b62a1 100644
--- a/src/Components/Components/src/Rendering/Renderer.Log.cs
+++ b/src/Components/Components/src/Rendering/Renderer.Log.cs
@@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
private static readonly Action _disposingComponent =
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}");
- private static readonly Action _handlingEvent =
- LoggerMessage.Define(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
+ private static readonly Action _handlingEvent =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState)
{
@@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
}
- internal static void HandlingEvent(ILogger logger, int eventHandlerId, UIEventArgs eventArgs)
+ internal static void HandlingEvent(ILogger logger, ulong eventHandlerId, UIEventArgs eventArgs)
{
_handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null);
}
diff --git a/src/Components/Components/src/Rendering/Renderer.cs b/src/Components/Components/src/Rendering/Renderer.cs
index cce99aca88..60fe23e423 100644
--- a/src/Components/Components/src/Rendering/Renderer.cs
+++ b/src/Components/Components/src/Rendering/Renderer.cs
@@ -17,16 +17,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
///
public abstract partial class Renderer : IDisposable
{
- private readonly ComponentFactory _componentFactory;
+ private readonly IServiceProvider _serviceProvider;
private readonly Dictionary _componentStateById = new Dictionary();
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
- private readonly Dictionary _eventBindings = new Dictionary();
- private readonly Dictionary _eventHandlerIdReplacements = new Dictionary();
+ private readonly Dictionary _eventBindings = new Dictionary();
+ private readonly Dictionary _eventHandlerIdReplacements = new Dictionary();
private readonly ILogger _logger;
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
private bool _isBatchInProgress;
- private int _lastEventHandlerId = 0;
+ private ulong _lastEventHandlerId;
private List _pendingTasks;
///
@@ -61,9 +61,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
throw new ArgumentNullException(nameof(loggerFactory));
}
- _componentFactory = new ComponentFactory(serviceProvider);
+ _serviceProvider = serviceProvider;
_logger = loggerFactory.CreateLogger();
- _componentFactory = new ComponentFactory(serviceProvider);
}
///
@@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// The type of the component to instantiate.
/// The component instance.
protected IComponent InstantiateComponent(Type componentType)
- => _componentFactory.InstantiateComponent(componentType);
+ => ComponentFactory.Instance.InstantiateComponent(_serviceProvider, componentType);
///
/// Associates the with the , assigning
@@ -186,7 +185,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
var componentState = new ComponentState(this, componentId, component, parentComponentState);
Log.InitializingComponent(_logger, componentState, parentComponentState);
_componentStateById.Add(componentId, componentState);
- component.Configure(new RenderHandle(this, componentId));
+ component.Attach(new RenderHandle(this, componentId));
return componentState;
}
@@ -207,7 +206,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
/// A which will complete once all asynchronous processing related to the event
/// has completed.
///
- public virtual Task DispatchEventAsync(int eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
+ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
{
EnsureSynchronizationContext();
@@ -355,7 +354,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
}
- internal void TrackReplacedEventHandlerId(int oldEventHandlerId, int newEventHandlerId)
+ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEventHandlerId)
{
// Tracking the chain of old->new replacements allows us to interpret incoming EventFieldInfo
// values even if they refer to an event handler ID that's since been superseded. This is essential
@@ -363,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
}
- private int FindLatestEventHandlerIdInChain(int eventHandlerId)
+ private ulong FindLatestEventHandlerIdInChain(ulong eventHandlerId)
{
while (_eventHandlerIdReplacements.TryGetValue(eventHandlerId, out var replacementEventHandlerId))
{
@@ -574,7 +573,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
}
- private void RemoveEventHandlerIds(ArrayRange eventHandlerIds, Task afterTaskIgnoreErrors)
+ private void RemoveEventHandlerIds(ArrayRange eventHandlerIds, Task afterTaskIgnoreErrors)
{
if (eventHandlerIds.Count == 0)
{
@@ -599,7 +598,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
// Factor out the async part into a separate local method purely so, in the
// synchronous case, there's no state machine or task construction
- async Task ContinueAfterTask(ArrayRange eventHandlerIds, Task afterTaskIgnoreErrors)
+ async Task ContinueAfterTask(ArrayRange eventHandlerIds, Task afterTaskIgnoreErrors)
{
// We need to delay the actual removal (e.g., until we've confirmed the client
// has processed the batch and hence can be sure not to reuse the handler IDs
@@ -638,7 +637,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
}
}
- private void UpdateRenderTreeToMatchClientState(int eventHandlerId, EventFieldInfo fieldInfo)
+ private void UpdateRenderTreeToMatchClientState(ulong eventHandlerId, EventFieldInfo fieldInfo)
{
var componentState = GetOptionalComponentState(fieldInfo.ComponentId);
if (componentState != null)
@@ -664,13 +663,15 @@ namespace Microsoft.AspNetCore.Components.Rendering
{
try
{
- disposable.Dispose();
+ componentState.Dispose();
}
catch (Exception exception)
{
HandleException(exception);
}
}
+
+ _batchBuilder.Dispose();
}
}
diff --git a/src/Components/Components/src/Routing/RouteEntry.cs b/src/Components/Components/src/Routing/RouteEntry.cs
index d96317151b..cff9420bd9 100644
--- a/src/Components/Components/src/Routing/RouteEntry.cs
+++ b/src/Components/Components/src/Routing/RouteEntry.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -26,8 +26,8 @@ namespace Microsoft.AspNetCore.Components.Routing
}
// Parameters will be lazily initialized.
- IDictionary parameters = null;
- for (int i = 0; i < Template.Segments.Length; i++)
+ Dictionary parameters = null;
+ for (var i = 0; i < Template.Segments.Length; i++)
{
var segment = Template.Segments[i];
var pathSegment = context.Segments[i];
@@ -39,23 +39,14 @@ namespace Microsoft.AspNetCore.Components.Routing
{
if (segment.IsParameter)
{
- GetParameters()[segment.Value] = matchedParameterValue;
+ parameters ??= new Dictionary(StringComparer.Ordinal);
+ parameters[segment.Value] = matchedParameterValue;
}
}
}
context.Parameters = parameters;
context.Handler = Handler;
-
- IDictionary GetParameters()
- {
- if (parameters == null)
- {
- parameters = new Dictionary();
- }
-
- return parameters;
- }
}
}
}
diff --git a/src/Components/Components/src/Routing/RouteTable.cs b/src/Components/Components/src/Routing/RouteTable.cs
index 4449c00f3c..029bc47657 100644
--- a/src/Components/Components/src/Routing/RouteTable.cs
+++ b/src/Components/Components/src/Routing/RouteTable.cs
@@ -1,12 +1,6 @@
// 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 System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Microsoft.AspNetCore.Components;
-
namespace Microsoft.AspNetCore.Components.Routing
{
internal class RouteTable
@@ -16,117 +10,13 @@ namespace Microsoft.AspNetCore.Components.Routing
Routes = routes;
}
- public RouteEntry[] Routes { get; set; }
-
- public static RouteTable Create(IEnumerable types)
- {
- var routes = new List();
- foreach (var type in types)
- {
- // We're deliberately using inherit = false here.
- //
- // RouteAttribute is defined as non-inherited, because inheriting a route attribute always causes an
- // ambiguity. You end up with two components (base class and derived class) with the same route.
- var routeAttributes = type.GetCustomAttributes(inherit: false);
-
- foreach (var routeAttribute in routeAttributes)
- {
- var template = TemplateParser.ParseTemplate(routeAttribute.Template);
- var entry = new RouteEntry(template, type);
- routes.Add(entry);
- }
- }
-
- return new RouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray());
- }
-
- public static IComparer RoutePrecedence { get; } = Comparer.Create(RouteComparison);
-
- ///
- /// Route precedence algorithm.
- /// We collect all the routes and sort them from most specific to
- /// less specific. The specificity of a route is given by the specificity
- /// of its segments and the position of those segments in the route.
- /// * A literal segment is more specific than a parameter segment.
- /// * A parameter segment with more constraints is more specific than one with fewer constraints
- /// * Segment earlier in the route are evaluated before segments later in the route.
- /// For example:
- /// /Literal is more specific than /Parameter
- /// /Route/With/{parameter} is more specific than /{multiple}/With/{parameters}
- /// /Product/{id:int} is more specific than /Product/{id}
- ///
- /// Routes can be ambiguous if:
- /// They are composed of literals and those literals have the same values (case insensitive)
- /// They are composed of a mix of literals and parameters, in the same relative order and the
- /// literals have the same values.
- /// For example:
- /// * /literal and /Literal
- /// /{parameter}/literal and /{something}/literal
- /// /{parameter:constraint}/literal and /{something:constraint}/literal
- ///
- /// To calculate the precedence we sort the list of routes as follows:
- /// * Shorter routes go first.
- /// * A literal wins over a parameter in precedence.
- /// * For literals with different values (case insensitive) we choose the lexical order
- /// * For parameters with different numbers of constraints, the one with more wins
- /// If we get to the end of the comparison routing we've detected an ambiguous pair of routes.
- ///
- internal static int RouteComparison(RouteEntry x, RouteEntry y)
- {
- var xTemplate = x.Template;
- var yTemplate = y.Template;
- if (xTemplate.Segments.Length != y.Template.Segments.Length)
- {
- return xTemplate.Segments.Length < y.Template.Segments.Length ? -1 : 1;
- }
- else
- {
- for (int i = 0; i < xTemplate.Segments.Length; i++)
- {
- var xSegment = xTemplate.Segments[i];
- var ySegment = yTemplate.Segments[i];
- if (!xSegment.IsParameter && ySegment.IsParameter)
- {
- return -1;
- }
- if (xSegment.IsParameter && !ySegment.IsParameter)
- {
- return 1;
- }
-
- if (xSegment.IsParameter)
- {
- if (xSegment.Constraints.Length > ySegment.Constraints.Length)
- {
- return -1;
- }
- else if (xSegment.Constraints.Length < ySegment.Constraints.Length)
- {
- return 1;
- }
- }
- else
- {
- var comparison = string.Compare(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase);
- if (comparison != 0)
- {
- return comparison;
- }
- }
- }
-
- throw new InvalidOperationException($@"The following routes are ambiguous:
-'{x.Template.TemplateText}' in '{x.Handler.FullName}'
-'{y.Template.TemplateText}' in '{y.Handler.FullName}'
-");
- }
- }
+ public RouteEntry[] Routes { get; }
internal void Route(RouteContext routeContext)
{
- foreach (var route in Routes)
+ for (var i = 0; i < Routes.Length; i++)
{
- route.Match(routeContext);
+ Routes[i].Match(routeContext);
if (routeContext.Handler != null)
{
return;
diff --git a/src/Components/Components/src/Routing/RouteTableFactory.cs b/src/Components/Components/src/Routing/RouteTableFactory.cs
new file mode 100644
index 0000000000..eae29a7530
--- /dev/null
+++ b/src/Components/Components/src/Routing/RouteTableFactory.cs
@@ -0,0 +1,137 @@
+// 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 System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Components.Routing;
+
+namespace Microsoft.AspNetCore.Components
+{
+ ///
+ /// Resolves components for an application.
+ ///
+ internal static class RouteTableFactory
+ {
+ private static readonly ConcurrentDictionary Cache =
+ new ConcurrentDictionary();
+ public static readonly IComparer RoutePrecedence = Comparer.Create(RouteComparison);
+
+ public static RouteTable Create(Assembly appAssembly)
+ {
+ if (Cache.TryGetValue(appAssembly, out var resolvedComponents))
+ {
+ return resolvedComponents;
+ }
+
+ var componentTypes = appAssembly.ExportedTypes.Where(t => typeof(IComponent).IsAssignableFrom(t));
+ var routeTable = Create(componentTypes);
+ Cache.TryAdd(appAssembly, routeTable);
+ return routeTable;
+ }
+
+ internal static RouteTable Create(IEnumerable componentTypes)
+ {
+ var routes = new List();
+ foreach (var type in componentTypes)
+ {
+ // We're deliberately using inherit = false here.
+ //
+ // RouteAttribute is defined as non-inherited, because inheriting a route attribute always causes an
+ // ambiguity. You end up with two components (base class and derived class) with the same route.
+ var routeAttributes = type.GetCustomAttributes(inherit: false);
+
+ foreach (var routeAttribute in routeAttributes)
+ {
+ var template = TemplateParser.ParseTemplate(routeAttribute.Template);
+ var entry = new RouteEntry(template, type);
+ routes.Add(entry);
+ }
+ }
+
+ return new RouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray());
+ }
+
+ ///
+ /// Route precedence algorithm.
+ /// We collect all the routes and sort them from most specific to
+ /// less specific. The specificity of a route is given by the specificity
+ /// of its segments and the position of those segments in the route.
+ /// * A literal segment is more specific than a parameter segment.
+ /// * A parameter segment with more constraints is more specific than one with fewer constraints
+ /// * Segment earlier in the route are evaluated before segments later in the route.
+ /// For example:
+ /// /Literal is more specific than /Parameter
+ /// /Route/With/{parameter} is more specific than /{multiple}/With/{parameters}
+ /// /Product/{id:int} is more specific than /Product/{id}
+ ///
+ /// Routes can be ambiguous if:
+ /// They are composed of literals and those literals have the same values (case insensitive)
+ /// They are composed of a mix of literals and parameters, in the same relative order and the
+ /// literals have the same values.
+ /// For example:
+ /// * /literal and /Literal
+ /// /{parameter}/literal and /{something}/literal
+ /// /{parameter:constraint}/literal and /{something:constraint}/literal
+ ///
+ /// To calculate the precedence we sort the list of routes as follows:
+ /// * Shorter routes go first.
+ /// * A literal wins over a parameter in precedence.
+ /// * For literals with different values (case insensitive) we choose the lexical order
+ /// * For parameters with different numbers of constraints, the one with more wins
+ /// If we get to the end of the comparison routing we've detected an ambiguous pair of routes.
+ ///
+ internal static int RouteComparison(RouteEntry x, RouteEntry y)
+ {
+ var xTemplate = x.Template;
+ var yTemplate = y.Template;
+ if (xTemplate.Segments.Length != y.Template.Segments.Length)
+ {
+ return xTemplate.Segments.Length < y.Template.Segments.Length ? -1 : 1;
+ }
+ else
+ {
+ for (var i = 0; i < xTemplate.Segments.Length; i++)
+ {
+ var xSegment = xTemplate.Segments[i];
+ var ySegment = yTemplate.Segments[i];
+ if (!xSegment.IsParameter && ySegment.IsParameter)
+ {
+ return -1;
+ }
+ if (xSegment.IsParameter && !ySegment.IsParameter)
+ {
+ return 1;
+ }
+
+ if (xSegment.IsParameter)
+ {
+ if (xSegment.Constraints.Length > ySegment.Constraints.Length)
+ {
+ return -1;
+ }
+ else if (xSegment.Constraints.Length < ySegment.Constraints.Length)
+ {
+ return 1;
+ }
+ }
+ else
+ {
+ var comparison = string.Compare(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase);
+ if (comparison != 0)
+ {
+ return comparison;
+ }
+ }
+ }
+
+ throw new InvalidOperationException($@"The following routes are ambiguous:
+'{x.Template.TemplateText}' in '{x.Handler.FullName}'
+'{y.Template.TemplateText}' in '{y.Handler.FullName}'
+");
+ }
+ }
+ }
+}
diff --git a/src/Components/Components/src/Routing/RouteTemplate.cs b/src/Components/Components/src/Routing/RouteTemplate.cs
index 1856ef20d2..bb59be07ee 100644
--- a/src/Components/Components/src/Routing/RouteTemplate.cs
+++ b/src/Components/Components/src/Routing/RouteTemplate.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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.
@@ -6,11 +6,9 @@ namespace Microsoft.AspNetCore.Components.Routing
{
internal class RouteTemplate
{
- public static readonly char[] Separators = new[] { '/' };
-
- public RouteTemplate(string TemplateText, TemplateSegment[] segments)
+ public RouteTemplate(string templateText, TemplateSegment[] segments)
{
- this.TemplateText = TemplateText;
+ TemplateText = templateText;
Segments = segments;
}
diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs
index 87a66a692a..451de47bc5 100644
--- a/src/Components/Components/src/Routing/Router.cs
+++ b/src/Components/Components/src/Routing/Router.cs
@@ -7,7 +7,6 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.AspNetCore.Components.Routing
{
@@ -57,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Routing
private RouteTable Routes { get; set; }
///
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
_logger = LoggerFactory.CreateLogger();
_renderHandle = renderHandle;
@@ -70,8 +69,7 @@ namespace Microsoft.AspNetCore.Components.Routing
public Task SetParametersAsync(ParameterCollection parameters)
{
parameters.SetParameterProperties(this);
- var types = ComponentResolver.ResolveComponents(AppAssembly);
- Routes = RouteTable.Create(types);
+ Routes = RouteTableFactory.Create(AppAssembly);
Refresh(isNavigationIntercepted: false);
return Task.CompletedTask;
}
diff --git a/src/Components/Components/test/Auth/AuthorizeViewTest.cs b/src/Components/Components/test/Auth/AuthorizeViewTest.cs
index 36a83cd935..d87eb9eaeb 100644
--- a/src/Components/Components/test/Auth/AuthorizeViewTest.cs
+++ b/src/Components/Components/test/Auth/AuthorizeViewTest.cs
@@ -427,6 +427,24 @@ namespace Microsoft.AspNetCore.Components
});
}
+ [Fact]
+ public void RejectsNonemptyScheme()
+ {
+ // Arrange
+ var authorizationService = new TestAuthorizationService();
+ var renderer = CreateTestRenderer(authorizationService);
+ var rootComponent = new TestAuthStateProviderComponent(builder =>
+ {
+ builder.OpenComponent(0);
+ builder.CloseComponent();
+ });
+ renderer.AssignRootComponentId(rootComponent);
+
+ // Act/Assert
+ var ex = Assert.Throws(rootComponent.TriggerRender);
+ Assert.Equal("The authorization data specifies an authentication scheme with value 'test scheme'. Authentication schemes cannot be specified for components.", ex.Message);
+ }
+
private static TestAuthStateProviderComponent WrapInAuthorizeView(
RenderFragment childContent = null,
RenderFragment authorizedContent = null,
@@ -481,7 +499,7 @@ namespace Microsoft.AspNetCore.Components
// recurse into all descendants because we're passing ChildContent
class NeverReRenderComponent : ComponentBase
{
- [Parameter] RenderFragment ChildContent { get; set; }
+ [Parameter] public RenderFragment ChildContent { get; set; }
protected override bool ShouldRender() => false;
@@ -557,5 +575,11 @@ namespace Microsoft.AspNetCore.Components
{
public string PolicyName { get; set; }
}
+
+ public class AuthorizeViewCoreWithScheme : AuthorizeViewCore
+ {
+ protected override IAuthorizeData[] GetAuthorizeData()
+ => new[] { new AuthorizeAttribute { AuthenticationSchemes = "test scheme" } };
+ }
}
}
diff --git a/src/Components/Components/test/BindConverterTest.cs b/src/Components/Components/test/BindConverterTest.cs
new file mode 100644
index 0000000000..c6fe1275cc
--- /dev/null
+++ b/src/Components/Components/test/BindConverterTest.cs
@@ -0,0 +1,307 @@
+// 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 System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text.Json;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components
+{
+ // This is some basic coverage, it's not in depth because there are many many APIs here
+ // and they mostly call through to CoreFx. We don't want to test the globalization details
+ // of .NET in detail where we can avoid it.
+ //
+ // Instead there's a sampling of things that have somewhat unique behavior or semantics.
+ public class BindConverterTest
+ {
+ [Fact]
+ public void FormatValue_Bool()
+ {
+ // Arrange
+ var value = true;
+ var expected = true;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_Bool_Generic()
+ {
+ // Arrange
+ var value = true;
+ var expected = true;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableBool()
+ {
+ // Arrange
+ var value = (bool?)true;
+ var expected = true;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableBool_Generic()
+ {
+ // Arrange
+ var value = true;
+ var expected = true;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableBoolNull()
+ {
+ // Arrange
+ var value = (bool?)null;
+ var expected = (bool?)null;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableBoolNull_Generic()
+ {
+ // Arrange
+ var value = (bool?)null;
+ var expected = (bool?)null;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_Int()
+ {
+ // Arrange
+ var value = 17;
+ var expected = "17";
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_Int_Generic()
+ {
+ // Arrange
+ var value = 17;
+ var expected = "17";
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableInt()
+ {
+ // Arrange
+ var value = (int?)17;
+ var expected = "17";
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableInt_Generic()
+ {
+ // Arrange
+ var value = 17;
+ var expected = "17";
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_DateTime()
+ {
+ // Arrange
+ var value = DateTime.Now;
+ var expected = value.ToString(CultureInfo.CurrentCulture);
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_DateTime_Format()
+ {
+ // Arrange
+ var value = DateTime.Now;
+ var expected = value.ToString("MM-yyyy", CultureInfo.InvariantCulture);
+
+ // Act
+ var actual = BindConverter.FormatValue(value, "MM-yyyy", CultureInfo.InvariantCulture);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_Enum()
+ {
+ // Arrange
+ var value = SomeLetters.A;
+ var expected = value.ToString();
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_Enum_OutOfRange()
+ {
+ // Arrange
+ var value = SomeLetters.A + 3;
+ var expected = value.ToString();
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void FormatValue_NullableEnum()
+ {
+ // Arrange
+ var value = (SomeLetters?)null;
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void FormatValue_TypeConverter()
+ {
+ // Arrange
+ var value = new Person()
+ {
+ Name = "Glenn",
+ Age = 47,
+ };
+
+ var expected = JsonSerializer.Serialize(value);
+
+ // Act
+ var actual = BindConverter.FormatValue(value);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ private enum SomeLetters
+ {
+ A,
+ B,
+ C,
+ Q,
+ }
+
+ [TypeConverter(typeof(PersonConverter))]
+ private class Person
+ {
+ public string Name { get; set; }
+
+ public int Age { get; set; }
+ }
+
+ private class PersonConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ {
+ return true;
+ }
+
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value is string text)
+ {
+ return JsonSerializer.Deserialize(text);
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return true;
+ }
+
+ return base.CanConvertTo(context, destinationType);
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return JsonSerializer.Serialize((Person)value);
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+ }
+}
diff --git a/src/Components/Components/test/CascadingParameterStateTest.cs b/src/Components/Components/test/CascadingParameterStateTest.cs
index 95d42dc24f..12ed03a445 100644
--- a/src/Components/Components/test/CascadingParameterStateTest.cs
+++ b/src/Components/Components/test/CascadingParameterStateTest.cs
@@ -374,7 +374,7 @@ namespace Microsoft.AspNetCore.Components.Test
{
var supplier = new CascadingValue();
var renderer = new TestRenderer();
- supplier.Configure(new RenderHandle(renderer, 0));
+ supplier.Attach(new RenderHandle(renderer, 0));
var supplierParams = new Dictionary
{
@@ -389,19 +389,19 @@ namespace Microsoft.AspNetCore.Components.Test
renderer.Dispatcher.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterCollection.FromDictionary(supplierParams))));
return supplier;
}
-
+
class ComponentWithNoParams : TestComponentBase
{
}
class ComponentWithNoCascadingParams : TestComponentBase
{
- [Parameter] bool SomeRegularParameter { get; set; }
+ [Parameter] public bool SomeRegularParameter { get; set; }
}
class ComponentWithCascadingParams : TestComponentBase
{
- [Parameter] bool RegularParam { get; set; }
+ [Parameter] public bool RegularParam { get; set; }
[CascadingParameter] internal ValueType1 CascadingParam1 { get; set; }
[CascadingParameter] internal ValueType2 CascadingParam2 { get; set; }
}
@@ -424,7 +424,7 @@ namespace Microsoft.AspNetCore.Components.Test
class TestComponentBase : IComponent
{
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
=> throw new NotImplementedException();
public Task SetParametersAsync(ParameterCollection parameters)
diff --git a/src/Components/Components/test/CascadingParameterTest.cs b/src/Components/Components/test/CascadingParameterTest.cs
index 5f6a01e364..766a1bd3b4 100644
--- a/src/Components/Components/test/CascadingParameterTest.cs
+++ b/src/Components/Components/test/CascadingParameterTest.cs
@@ -381,7 +381,7 @@ namespace Microsoft.AspNetCore.Components.Test
public int NumRenders { get; private set; }
[CascadingParameter] T CascadingParameter { get; set; }
- [Parameter] string RegularParameter { get; set; }
+ [Parameter] public string RegularParameter { get; set; }
public override async Task SetParametersAsync(ParameterCollection parameters)
{
diff --git a/src/Components/Components/test/ComponentFactoryTest.cs b/src/Components/Components/test/ComponentFactoryTest.cs
new file mode 100644
index 0000000000..0d77c0f24a
--- /dev/null
+++ b/src/Components/Components/test/ComponentFactoryTest.cs
@@ -0,0 +1,168 @@
+// 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 System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components
+{
+ public class ComponentFactoryTest
+ {
+ [Fact]
+ public void InstantiateComponent_CreatesInstance()
+ {
+ // Arrange
+ var componentType = typeof(EmptyComponent);
+ var factory = new ComponentFactory();
+
+ // Act
+ var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
+
+ // Assert
+ Assert.NotNull(instance);
+ Assert.IsType(instance);
+ }
+
+ [Fact]
+ public void InstantiateComponent_AssignsPropertiesWithInjectAttribute()
+ {
+ // Arrange
+ var componentType = typeof(ComponentWithInjectProperties);
+ var factory = new ComponentFactory();
+
+ // Act
+ var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
+
+ // Assert
+ Assert.NotNull(instance);
+ var component = Assert.IsType(instance);
+ // Public, and non-public properties, and properties with non-public setters should get assigned
+ Assert.NotNull(component.Property1);
+ Assert.NotNull(component.GetProperty2());
+ Assert.NotNull(component.Property3);
+ Assert.NotNull(component.Property4);
+ }
+
+ [Fact]
+ public void InstantiateComponent_AssignsPropertiesWithInjectAttributeOnBaseType()
+ {
+ // Arrange
+ var componentType = typeof(DerivedComponent);
+ var factory = new ComponentFactory();
+
+ // Act
+ var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
+
+ // Assert
+ Assert.NotNull(instance);
+ var component = Assert.IsType(instance);
+ Assert.NotNull(component.Property1);
+ Assert.NotNull(component.GetProperty2());
+ Assert.NotNull(component.Property3);
+
+ // Property on derived type without [Inject] should not be assigned
+ Assert.Null(component.Property4);
+ // Property on the base type with the [Inject] attribute should
+ Assert.NotNull(((ComponentWithInjectProperties)component).Property4);
+ }
+
+ [Fact]
+ public void InstantiateComponent_IgnoresPropertiesWithoutInjectAttribute()
+ {
+ // Arrange
+ var componentType = typeof(ComponentWithNonInjectableProperties);
+ var factory = new ComponentFactory();
+
+ // Act
+ var instance = factory.InstantiateComponent(GetServiceProvider(), componentType);
+
+ // Assert
+ Assert.NotNull(instance);
+ var component = Assert.IsType(instance);
+ // Public, and non-public properties, and properties with non-public setters should get assigned
+ Assert.NotNull(component.Property1);
+ Assert.Null(component.Property2);
+ }
+
+ private static IServiceProvider GetServiceProvider()
+ {
+ return new ServiceCollection()
+ .AddTransient()
+ .AddTransient()
+ .BuildServiceProvider();
+ }
+
+ private class EmptyComponent : IComponent
+ {
+ public void Attach(RenderHandle renderHandle)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetParametersAsync(ParameterCollection parameters)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class ComponentWithInjectProperties : IComponent
+ {
+ [Inject]
+ public TestService1 Property1 { get; set; }
+
+ [Inject]
+ private TestService2 Property2 { get; set; }
+
+ [Inject]
+ public TestService1 Property3 { get; private set; }
+
+ [Inject]
+ public TestService1 Property4 { get; set; }
+
+ public TestService2 GetProperty2() => Property2;
+
+ public void Attach(RenderHandle renderHandle)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetParametersAsync(ParameterCollection parameters)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class ComponentWithNonInjectableProperties : IComponent
+ {
+ [Inject]
+ public TestService1 Property1 { get; set; }
+
+ public TestService1 Property2 { get; set; }
+
+ public void Attach(RenderHandle renderHandle)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetParametersAsync(ParameterCollection parameters)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class DerivedComponent : ComponentWithInjectProperties
+ {
+ public new TestService2 Property4 { get; set; }
+
+ [Inject]
+ public TestService2 Property5 { get; set; }
+ }
+
+ public class TestService1 { }
+ public class TestService2 { }
+ }
+}
diff --git a/src/Components/Components/test/DependencyInjectionTest.cs b/src/Components/Components/test/DependencyInjectionTest.cs
index b6ccabbe49..fedbcba993 100644
--- a/src/Components/Components/test/DependencyInjectionTest.cs
+++ b/src/Components/Components/test/DependencyInjectionTest.cs
@@ -194,7 +194,7 @@ namespace Microsoft.AspNetCore.Components.Test
// not throw, then be sure also to add a test to verify that injection
// occurs before lifecycle methods.
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
=> throw new NotImplementedException();
public Task SetParametersAsync(ParameterCollection parameters)
diff --git a/src/Components/Components/test/ElementReferenceTest.cs b/src/Components/Components/test/ElementReferenceTest.cs
new file mode 100644
index 0000000000..39c947aaf0
--- /dev/null
+++ b/src/Components/Components/test/ElementReferenceTest.cs
@@ -0,0 +1,82 @@
+// 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.Text.Json;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components
+{
+ public class ElementReferenceTest
+ {
+ [Fact]
+ public void Serializing_Works()
+ {
+ // Arrange
+ var elementReference = ElementReference.CreateWithUniqueId();
+ var expected = $"{{\"__internalId\":\"{elementReference.Id}\"}}";
+
+ // Act
+ var json = JsonSerializer.Serialize(elementReference, JsonSerializerOptionsProvider.Options);
+
+ // Assert
+ Assert.Equal(expected, json);
+ }
+
+ [Fact]
+ public void Deserializing_Works()
+ {
+ // Arrange
+ var id = ElementReference.CreateWithUniqueId().Id;
+ var json = $"{{\"__internalId\":\"{id}\"}}";
+
+ // Act
+ var elementReference = JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options);
+
+ // Assert
+ Assert.Equal(id, elementReference.Id);
+ }
+
+ [Fact]
+ public void Deserializing_WithFormatting_Works()
+ {
+ // Arrange
+ var id = ElementReference.CreateWithUniqueId().Id;
+ var json =
+@$"{{
+ ""__internalId"": ""{id}""
+}}";
+
+ // Act
+ var elementReference = JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options);
+
+ // Assert
+ Assert.Equal(id, elementReference.Id);
+ }
+
+ [Fact]
+ public void Deserializing_Throws_IfUnknownPropertyAppears()
+ {
+ // Arrange
+ var json = "{\"id\":\"some-value\"}";
+
+ // Act
+ var ex = Assert.Throws(() => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options));
+
+ // Assert
+ Assert.Equal("Unexpected JSON property 'id'.", ex.Message);
+ }
+
+ [Fact]
+ public void Deserializing_Throws_IfIdIsNotSpecified()
+ {
+ // Arrange
+ var json = "{}";
+
+ // Act
+ var ex = Assert.Throws(() => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options));
+
+ // Assert
+ Assert.Equal("__internalId is required.", ex.Message);
+ }
+ }
+}
diff --git a/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs b/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs
index 62402f2265..220e9285ab 100644
--- a/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs
+++ b/src/Components/Components/test/EventCallbackFactoryBinderExtensionsTest.cs
@@ -662,7 +662,7 @@ namespace Microsoft.AspNetCore.Components
return item.InvokeAsync(arg);
}
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
throw new System.NotImplementedException();
}
diff --git a/src/Components/Components/test/EventCallbackFactoryTest.cs b/src/Components/Components/test/EventCallbackFactoryTest.cs
index 714c73c656..dc9a3bfd30 100644
--- a/src/Components/Components/test/EventCallbackFactoryTest.cs
+++ b/src/Components/Components/test/EventCallbackFactoryTest.cs
@@ -635,7 +635,7 @@ namespace Microsoft.AspNetCore.Components
return Task.CompletedTask;
}
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
throw new NotImplementedException();
}
diff --git a/src/Components/Components/test/EventCallbackTest.cs b/src/Components/Components/test/EventCallbackTest.cs
index 37d6b354ff..e390735fa8 100644
--- a/src/Components/Components/test/EventCallbackTest.cs
+++ b/src/Components/Components/test/EventCallbackTest.cs
@@ -449,7 +449,7 @@ namespace Microsoft.AspNetCore.Components
return item.InvokeAsync(arg);
}
- public void Configure(RenderHandle renderHandle) => throw new NotImplementedException();
+ public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
public Task SetParametersAsync(ParameterCollection parameters) => throw new NotImplementedException();
}
diff --git a/src/Components/Components/test/PageDisplayTest.cs b/src/Components/Components/test/PageDisplayTest.cs
index 8e834a7e4f..e73e7e7083 100644
--- a/src/Components/Components/test/PageDisplayTest.cs
+++ b/src/Components/Components/test/PageDisplayTest.cs
@@ -220,7 +220,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class RootLayout : AutoRenderComponent
{
[Parameter]
- RenderFragment Body { get; set; }
+ public RenderFragment Body { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
@@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class NestedLayout : AutoRenderComponent
{
[Parameter]
- RenderFragment Body { get; set; }
+ public RenderFragment Body { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
diff --git a/src/Components/Components/test/ParameterCollectionAssignmentExtensionsTest.cs b/src/Components/Components/test/ParameterCollectionAssignmentExtensionsTest.cs
index 7193081888..131697490f 100644
--- a/src/Components/Components/test/ParameterCollectionAssignmentExtensionsTest.cs
+++ b/src/Components/Components/test/ParameterCollectionAssignmentExtensionsTest.cs
@@ -364,11 +364,11 @@ namespace Microsoft.AspNetCore.Components.Test
// "internal" to show we're not requiring public accessors, but also
// to keep the assertions simple in the tests
- [Parameter] internal int IntProp { get; set; }
- [Parameter] internal string StringProp { get; set; }
+ [Parameter] public int IntProp { get; set; }
+ [Parameter] public string StringProp { get; set; }
// Also a truly private one to show there's nothing special about 'internal'
- [Parameter] private object ObjectProp { get; set; }
+ [Parameter] public object ObjectProp { get; set; }
public static string ObjectPropName => nameof(ObjectProp);
public object ObjectPropCurrentValue
@@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Components.Test
class HasPropertyWhoseSetterThrows
{
[Parameter]
- internal string StringProp
+ public string StringProp
{
get => string.Empty;
set => throw new InvalidOperationException("This setter throws");
@@ -395,37 +395,37 @@ namespace Microsoft.AspNetCore.Components.Test
class HasInheritedProperties : HasInstanceProperties
{
- [Parameter] internal int DerivedClassIntProp { get; set; }
+ [Parameter] public int DerivedClassIntProp { get; set; }
}
class HasParametersVaryingOnlyByCase
{
- [Parameter] internal object MyValue { get; set; }
- [Parameter] internal object Myvalue { get; set; }
+ [Parameter] public object MyValue { get; set; }
+ [Parameter] public object Myvalue { get; set; }
}
class HasParameterClashingWithInherited : HasInstanceProperties
{
- [Parameter] new int IntProp { get; set; }
+ [Parameter] public new int IntProp { get; set; }
}
class HasCaptureUnmatchedValuesProperty
{
- [Parameter] internal int IntProp { get; set; }
- [Parameter] internal string StringProp { get; set; }
- [Parameter] internal object ObjectProp { get; set; }
- [Parameter(CaptureUnmatchedValues = true)] internal IReadOnlyDictionary CaptureUnmatchedValues { get; set; }
+ [Parameter] public int IntProp { get; set; }
+ [Parameter] public string StringProp { get; set; }
+ [Parameter] public object ObjectProp { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary CaptureUnmatchedValues { get; set; }
}
class HasDupliateCaptureUnmatchedValuesProperty
{
- [Parameter(CaptureUnmatchedValues = true)] internal Dictionary CaptureUnmatchedValuesProp1 { get; set; }
- [Parameter(CaptureUnmatchedValues = true)] internal IDictionary CaptureUnmatchedValuesProp2 { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)] public Dictionary CaptureUnmatchedValuesProp1 { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)] public IDictionary CaptureUnmatchedValuesProp2 { get; set; }
}
class HasWrongTypeCaptureUnmatchedValuesProperty
{
- [Parameter(CaptureUnmatchedValues = true)] internal KeyValuePair[] CaptureUnmatchedValuesProp { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)] public KeyValuePair[] CaptureUnmatchedValuesProp { get; set; }
}
class ParameterCollectionBuilder : IEnumerable
@@ -454,7 +454,7 @@ namespace Microsoft.AspNetCore.Components.Test
class FakeComponent : IComponent
{
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
=> throw new NotImplementedException();
public Task SetParametersAsync(ParameterCollection parameters)
diff --git a/src/Components/Components/test/ParameterCollectionTest.cs b/src/Components/Components/test/ParameterCollectionTest.cs
index 66620132d0..9e9b8da975 100644
--- a/src/Components/Components/test/ParameterCollectionTest.cs
+++ b/src/Components/Components/test/ParameterCollectionTest.cs
@@ -342,7 +342,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class FakeComponent : IComponent
{
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
=> throw new NotImplementedException();
public Task SetParametersAsync(ParameterCollection parameters)
diff --git a/src/Components/Components/test/RenderTreeBuilderTest.cs b/src/Components/Components/test/RenderTreeBuilderTest.cs
index 6d03c1261c..3207c31e54 100644
--- a/src/Components/Components/test/RenderTreeBuilderTest.cs
+++ b/src/Components/Components/test/RenderTreeBuilderTest.cs
@@ -1558,22 +1558,45 @@ namespace Microsoft.AspNetCore.Components.Test
}
[Fact]
- public void CannotAddNullKey()
+ public void IgnoresNullElementKey()
{
- // Although we could translate 'null' into either some default "null key"
- // instance, or just no-op the call, it almost certainly indicates a programming
- // error so it's better to fail.
-
// Arrange
var builder = new RenderTreeBuilder(new TestRenderer());
- // Act/Assert
- var ex = Assert.Throws(() =>
- {
- builder.OpenElement(0, "elem");
- builder.SetKey(null);
- });
- Assert.Equal("value", ex.ParamName);
+ // Act
+ builder.OpenElement(0, "elem");
+ builder.SetKey(null);
+ builder.CloseElement();
+
+ // Assert
+ Assert.Collection(
+ builder.GetFrames().AsEnumerable(),
+ frame =>
+ {
+ AssertFrame.Element(frame, "elem", 1, 0);
+ Assert.Null(frame.ElementKey);
+ });
+ }
+
+ [Fact]
+ public void IgnoresNullComponentKey()
+ {
+ // Arrange
+ var builder = new RenderTreeBuilder(new TestRenderer());
+
+ // Act
+ builder.OpenComponent(0);
+ builder.SetKey(null);
+ builder.CloseComponent();
+
+ // Assert
+ Assert.Collection(
+ builder.GetFrames().AsEnumerable(),
+ frame =>
+ {
+ AssertFrame.Component(frame, 1, 0);
+ Assert.Null(frame.ComponentKey);
+ });
}
[Fact]
@@ -1787,7 +1810,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class TestComponent : IComponent
{
- public void Configure(RenderHandle renderHandle) { }
+ public void Attach(RenderHandle renderHandle) { }
public Task SetParametersAsync(ParameterCollection parameters)
=> throw new NotImplementedException();
diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs
index 5054d574b4..28d9193ea0 100644
--- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs
+++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs
@@ -13,11 +13,12 @@ using Xunit;
namespace Microsoft.AspNetCore.Components.Test
{
- public class RenderTreeDiffBuilderTest
+ public class RenderTreeDiffBuilderTest : IDisposable
{
private readonly Renderer renderer;
private readonly RenderTreeBuilder oldTree;
private readonly RenderTreeBuilder newTree;
+ private RenderBatchBuilder batchBuilder;
public RenderTreeDiffBuilderTest()
{
@@ -26,6 +27,14 @@ namespace Microsoft.AspNetCore.Components.Test
newTree = new RenderTreeBuilder(renderer);
}
+ void IDisposable.Dispose()
+ {
+ renderer.Dispose();
+ ((IDisposable)oldTree).Dispose();
+ ((IDisposable)newTree).Dispose();
+ batchBuilder?.Dispose();
+ }
+
[Theory]
[MemberData(nameof(RecognizesEquivalentFramesAsSameCases))]
public void RecognizesEquivalentFramesAsSame(RenderFragment appendFragment)
@@ -208,7 +217,8 @@ namespace Microsoft.AspNetCore.Components.Test
oldTree.SetKey("retained key");
oldTree.AddAttribute(1, "ParamName", "Param old value");
oldTree.CloseComponent();
- GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
+ using var initial = new RenderTreeBuilder(renderer);
+ GetRenderedBatch(initial, oldTree, false); // Assign initial IDs
var oldComponent = GetComponents(oldTree).Single();
newTree.OpenComponent(0);
@@ -227,12 +237,12 @@ namespace Microsoft.AspNetCore.Components.Test
// param on the second component.
// Act
- var batch = GetRenderedBatch(initializeFromFrames: false);
+ var batchBuilder = GetRenderedBatch(initializeFromFrames: false);
var newComponents = GetComponents(newTree);
// Assert: Inserts new component at position 0
- Assert.Equal(1, batch.UpdatedComponents.Count);
- Assert.Collection(batch.UpdatedComponents.Array[0].Edits,
+ Assert.Equal(1, batchBuilder.UpdatedComponents.Count);
+ Assert.Collection(batchBuilder.UpdatedComponents.Array[0].Edits,
entry => AssertEdit(entry, RenderTreeEditType.PrependFrame, 0));
// Assert: Retains old component instance in position 1, and updates its params
@@ -255,7 +265,8 @@ namespace Microsoft.AspNetCore.Components.Test
oldTree.CloseComponent();
// Instantiate initial components
- GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
+ using var initial = new RenderTreeBuilder(renderer);
+ GetRenderedBatch(initial, oldTree, false);
var oldComponents = GetComponents(oldTree);
newTree.OpenComponent(0);
@@ -286,7 +297,8 @@ namespace Microsoft.AspNetCore.Components.Test
oldTree.CloseComponent();
// Instantiate initial component
- GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false);
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ GetRenderedBatch(renderTreeBuilder, oldTree, false);
var oldComponent = GetComponents(oldTree).Single();
Assert.NotNull(oldComponent);
@@ -315,86 +327,37 @@ namespace Microsoft.AspNetCore.Components.Test
}
[Fact]
- public void HandlesClashingKeys_FirstUsage()
+ public void RejectsClashingKeysInOldTree()
{
- // This scenario is problematic for the algorithm if it uses a "first key
- // usage wins" policy for duplicate keys. It would not end up with attrib1b
- // anywhere in the output, because whenever it sees key1 in oldTree, it tries
- // to diff against the first usage of key1 in newTree, which has attrib1a.
-
- // However, because of the actual "duplicated keys are excluded from the
- // dictionary match" policy, we don't preserve any of the key1 items, and
- // the diff is valid.
-
// Arrange
- AddWithKey(oldTree, "key3", "attrib3");
- AddWithKey(oldTree, "key1", "attrib1a");
AddWithKey(oldTree, "key1", "attrib1a");
AddWithKey(oldTree, "key2", "attrib2");
+ AddWithKey(oldTree, "key1", "attrib3");
- AddWithKey(newTree, "key1", "attrib1a");
- AddWithKey(newTree, "key2", "attrib2");
AddWithKey(newTree, "key1", "attrib1b");
+ AddWithKey(newTree, "key2", "attrib2");
+ AddWithKey(newTree, "key3", "attrib3");
- // Act
- var (result, referenceFrames) = GetSingleUpdatedComponent();
-
- // Assert
- Assert.Collection(result.Edits,
- // Insert key1+attrib1a at the top
- edit =>
- {
- AssertEdit(edit, RenderTreeEditType.PrependFrame, 0);
- Assert.Equal("attrib1a", referenceFrames[edit.ReferenceFrameIndex + 1].AttributeValue);
- },
- // Delete key3+attrib3
- edit => AssertEdit(edit, RenderTreeEditType.RemoveFrame, 1),
- // Delete key1+attrib1a
- edit => AssertEdit(edit, RenderTreeEditType.RemoveFrame, 1),
- // Delete the other key1+attrib1a
- edit => AssertEdit(edit, RenderTreeEditType.RemoveFrame, 1),
- // Insert key1+attrib1b at the bottom
- edit =>
- {
- AssertEdit(edit, RenderTreeEditType.PrependFrame, 2);
- Assert.Equal("attrib1b", referenceFrames[edit.ReferenceFrameIndex + 1].AttributeValue);
- });
+ // Act/Assert
+ var ex = Assert.Throws(() => GetSingleUpdatedComponent());
+ Assert.Equal("More than one sibling has the same key value, 'key1'. Key values must be unique.", ex.Message);
}
[Fact]
- public void HandlesClashingKeys_LastUsage()
+ public void RejectsClashingKeysInNewTree()
{
- // This scenario is problematic for the algorithm if it uses a "last key
- // usage wins" policy for duplicate keys. It would not end up with attrib1b
- // anywhere in the output, because when it sees key1 in oldTree, it tries
- // to diff against the last usage of key1 in newTree, which has attrib1a.
-
- // However, because of the actual "duplicated keys are excluded from the
- // dictionary match" policy, we don't preserve any of the key1 items, and
- // the diff is valid.
-
// Arrange
AddWithKey(oldTree, "key1", "attrib1a");
AddWithKey(oldTree, "key2", "attrib2");
- AddWithKey(oldTree, "key1", "attrib1b");
+ AddWithKey(oldTree, "key3", "attrib3");
- AddWithKey(newTree, "key2", "attrib2");
AddWithKey(newTree, "key1", "attrib1b");
- AddWithKey(newTree, "key1", "attrib1a");
+ AddWithKey(newTree, "key2", "attrib2");
+ AddWithKey(newTree, "key1", "attrib3");
- // Act
- var (result, referenceFrames) = GetSingleUpdatedComponent();
-
- // Assert
- Assert.Collection(result.Edits,
- // Delete key1+attrib1a
- edit => AssertEdit(edit, RenderTreeEditType.RemoveFrame, 0),
- // Insert a new key1+attrib1a at the bottom
- edit =>
- {
- AssertEdit(edit, RenderTreeEditType.PrependFrame, 2);
- Assert.Equal("attrib1a", referenceFrames[edit.ReferenceFrameIndex + 1].AttributeValue);
- });
+ // Act/Assert
+ var ex = Assert.Throws(() => GetSingleUpdatedComponent());
+ Assert.Equal("More than one sibling has the same key value, 'key1'. Key values must be unique.", ex.Message);
}
[Fact]
@@ -773,10 +736,11 @@ namespace Microsoft.AspNetCore.Components.Test
// Arrange
oldTree.OpenComponent(123);
oldTree.CloseComponent();
- GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
+ using var initial = new RenderTreeBuilder(renderer);
+ GetRenderedBatch(initial, oldTree, false); // Assign initial IDs
newTree.OpenComponent(123);
newTree.CloseComponent();
- var batchBuilder = new RenderBatchBuilder();
+ using var batchBuilder = new RenderBatchBuilder();
// Act
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, oldTree.GetFrames(), newTree.GetFrames());
@@ -884,7 +848,7 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.CloseElement();
// Act
- var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
+ var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
var removedEventHandlerFrame = oldTree.GetFrames().Array[2];
// Assert
@@ -895,10 +859,10 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(0, entry.ReferenceFrameIndex);
});
AssertFrame.Attribute(referenceFrames[0], "onbar", addedHandler);
- Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
+ Assert.NotEqual(default, removedEventHandlerFrame.AttributeEventHandlerId);
Assert.Equal(
new[] { removedEventHandlerFrame.AttributeEventHandlerId },
- batch.DisposedEventHandlerIDs.AsEnumerable());
+ batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
}
[Fact]
@@ -1588,7 +1552,9 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.CloseComponent(); //
newTree.CloseElement(); //
- RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
+ using var batchBuilder = new RenderBatchBuilder();
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
var originalFakeComponentInstance = oldTree.GetFrames().Array[2].Component;
var originalFakeComponent2Instance = oldTree.GetFrames().Array[3].Component;
@@ -1618,7 +1584,7 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.CloseElement();
// Act
- var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
+ var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
var oldAttributeFrame = oldTree.GetFrames().Array[1];
var newAttributeFrame = newTree.GetFrames().Array[1];
@@ -1626,9 +1592,9 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Empty(result.Edits);
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
- Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
+ Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
- Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
+ Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
}
[Fact]
@@ -1645,7 +1611,7 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.CloseElement();
// Act
- var (result, referenceFrames, batch) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
+ var (result, referenceFrames, batchBuilder) = GetSingleUpdatedComponentWithBatch(initializeFromFrames: true);
var oldAttributeFrame = oldTree.GetFrames().Array[1];
var newAttributeFrame = newTree.GetFrames().Array[2];
@@ -1653,9 +1619,9 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Single(result.Edits);
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
- Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
+ Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
- Assert.Empty(batch.DisposedEventHandlerIDs.AsEnumerable());
+ Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
}
[Fact]
@@ -1672,7 +1638,9 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
newTree.CloseComponent();
- RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
+ using var batchBuilder = new RenderBatchBuilder();
+ using var renderTree = new RenderTreeBuilder(renderer);
+ RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames());
var originalComponentInstance = (FakeComponent)oldTree.GetFrames().Array[0].Component;
// Act
@@ -1710,7 +1678,9 @@ namespace Microsoft.AspNetCore.Components.Test
tree.CloseComponent();
}
- RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
+ using var batchBuilder = new RenderBatchBuilder();
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
var originalComponentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
Assert.Equal(1, originalComponentInstance.SetParametersCallCount);
@@ -1738,7 +1708,9 @@ namespace Microsoft.AspNetCore.Components.Test
tree.CloseComponent();
}
- RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
+ using var batchBuilder = new RenderBatchBuilder();
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTreeBuilder.GetFrames(), oldTree.GetFrames());
var componentInstance = (CaptureSetParametersComponent)oldTree.GetFrames().Array[0].Component;
Assert.Equal(1, componentInstance.SetParametersCallCount);
@@ -1762,8 +1734,9 @@ namespace Microsoft.AspNetCore.Components.Test
newTree.OpenComponent(30); //
newTree.CloseComponent(); //
- var batchBuilder = new RenderBatchBuilder();
- RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, new RenderTreeBuilder(renderer).GetFrames(), oldTree.GetFrames());
+ using var batchBuilder = new RenderBatchBuilder();
+ using var renderTree = new RenderTreeBuilder(renderer);
+ RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, renderTree.GetFrames(), oldTree.GetFrames());
// Act/Assert
// Note that we track NonDisposableComponent was disposed even though it's not IDisposable,
@@ -1972,7 +1945,8 @@ namespace Microsoft.AspNetCore.Components.Test
oldTree.AddAttribute(1, nameof(FakeComponent.StringProperty), "Second param");
oldTree.CloseComponent();
- GetRenderedBatch(new RenderTreeBuilder(renderer), oldTree, false); // Assign initial IDs
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ GetRenderedBatch(renderTreeBuilder, oldTree, false); // Assign initial IDs
var oldComponents = GetComponents(oldTree);
newTree.OpenComponent(0);
@@ -2173,12 +2147,19 @@ namespace Microsoft.AspNetCore.Components.Test
{
if (initializeFromFrames)
{
- var emptyFrames = new RenderTreeBuilder(renderer).GetFrames();
+ using var renderTreeBuilder = new RenderTreeBuilder(renderer);
+ using var initializeBatchBuilder = new RenderBatchBuilder();
+
+ var emptyFrames = renderTreeBuilder.GetFrames();
var oldFrames = from.GetFrames();
- RenderTreeDiffBuilder.ComputeDiff(renderer, new RenderBatchBuilder(), 0, emptyFrames, oldFrames);
+
+ RenderTreeDiffBuilder.ComputeDiff(renderer, initializeBatchBuilder, 0, emptyFrames, oldFrames);
}
- var batchBuilder = new RenderBatchBuilder();
+ batchBuilder?.Dispose();
+ // This gets disposed as part of the test type's Dispose
+ batchBuilder = new RenderBatchBuilder();
+
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, batchBuilder, 0, from.GetFrames(), to.GetFrames());
batchBuilder.UpdatedComponentDiffs.Append(diff);
return batchBuilder.ToBatch();
@@ -2224,23 +2205,23 @@ namespace Microsoft.AspNetCore.Components.Test
private class FakeComponent : IComponent
{
[Parameter]
- internal int IntProperty { get; set; }
+ public int IntProperty { get; set; }
[Parameter]
- internal string StringProperty { get; set; }
+ public string StringProperty { get; set; }
[Parameter]
- internal object ObjectProperty { get; set; }
+ public object ObjectProperty { get; set; }
[Parameter]
- internal string ReadonlyProperty { get; private set; }
+ public string ReadonlyProperty { get; private set; }
[Parameter]
- string PrivateProperty { get; set; }
+ public string PrivateProperty { get; set; }
public string NonParameterProperty { get; set; }
- public void Configure(RenderHandle renderHandle) { }
+ public void Attach(RenderHandle renderHandle) { }
public Task SetParametersAsync(ParameterCollection parameters)
{
parameters.SetParameterProperties(this);
@@ -2250,7 +2231,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class FakeComponent2 : IComponent
{
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
}
@@ -2261,7 +2242,7 @@ namespace Microsoft.AspNetCore.Components.Test
{
public int SetParametersCallCount { get; private set; }
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
}
@@ -2277,14 +2258,14 @@ namespace Microsoft.AspNetCore.Components.Test
public int DisposalCount { get; private set; }
public void Dispose() => DisposalCount++;
- public void Configure(RenderHandle renderHandle) { }
+ public void Attach(RenderHandle renderHandle) { }
public Task SetParametersAsync(ParameterCollection parameters) => Task.CompletedTask;
}
private class NonDisposableComponent : IComponent
{
- public void Configure(RenderHandle renderHandle) { }
+ public void Attach(RenderHandle renderHandle) { }
public Task SetParametersAsync(ParameterCollection parameters) => Task.CompletedTask;
}
diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs
index 5b4baef88c..9cccfafefb 100644
--- a/src/Components/Components/test/RendererTest.cs
+++ b/src/Components/Components/test/RendererTest.cs
@@ -3313,7 +3313,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
var attributeFrame = batch2.ReferenceFrames[edit.ReferenceFrameIndex];
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action));
- Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
+ Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
});
}
@@ -3365,7 +3365,7 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
var attributeFrame = latestBatch.ReferenceFrames[edit.ReferenceFrameIndex];
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action));
- Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
+ Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
});
}
@@ -3399,7 +3399,7 @@ namespace Microsoft.AspNetCore.Components.Test
_renderFragment = renderFragment;
}
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}
@@ -3434,7 +3434,7 @@ namespace Microsoft.AspNetCore.Components.Test
private class MessageComponent : AutoRenderComponent
{
[Parameter]
- internal string Message { get; set; }
+ public string Message { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
@@ -3444,9 +3444,9 @@ namespace Microsoft.AspNetCore.Components.Test
private class MyStrongComponent : AutoRenderComponent
{
- [Parameter(CaptureUnmatchedValues = true)] internal IDictionary Attributes { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)] public IDictionary Attributes { get; set; }
- [Parameter] internal string Text { get; set; }
+ [Parameter] public string Text { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
@@ -3460,17 +3460,17 @@ namespace Microsoft.AspNetCore.Components.Test
private class FakeComponent : IComponent
{
[Parameter]
- internal int IntProperty { get; private set; }
+ public int IntProperty { get; private set; }
[Parameter]
- internal string StringProperty { get; private set; }
+ public string StringProperty { get; private set; }
[Parameter]
- internal object ObjectProperty { get; set; }
+ public object ObjectProperty { get; set; }
public RenderHandle RenderHandle { get; private set; }
- public void Configure(RenderHandle renderHandle)
+ public void Attach(RenderHandle renderHandle)
=> RenderHandle = renderHandle;
public Task SetParametersAsync(ParameterCollection parameters)
@@ -3483,28 +3483,28 @@ namespace Microsoft.AspNetCore.Components.Test
private class EventComponent : AutoRenderComponent, IComponent, IHandleEvent
{
[Parameter]
- internal Action OnTest { get; set; }
+ public Action