diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 300305948a..7e2edc2653 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -136,6 +136,7 @@ stages: -pack -noBuildDeps $(_BuildArgs) + condition: ne(variables['Build.Reason'], 'PullRequest') displayName: Build SiteExtension # This runs code-signing on all packages, zips, and jar files as defined in build/CodeSign.targets. If https://github.com/dotnet/arcade/issues/1957 is resolved, @@ -453,7 +454,7 @@ stages: - name: Windows_Test_Dumps path: artifacts/dumps/ publishOnError: true - includeForks: false + includeForks: true - name: Windows_Test_Logs path: artifacts/log/ publishOnError: true @@ -481,7 +482,7 @@ stages: - name: Windows_Test_Templates_Dumps path: artifacts/dumps/ publishOnError: true - includeForks: false + includeForks: true - name: Windows_Test_Templates_Logs path: artifacts/log/ publishOnError: true @@ -563,6 +564,27 @@ stages: publishOnError: true includeForks: true + # Helix ARM64 + - template: jobs/default-build.yml + parameters: + jobName: Helix_arm64 + jobDisplayName: "Tests: Helix ARM64" + agentOs: Linux + timeoutInMinutes: 240 + steps: + - script: ./restore.sh -ci + displayName: Restore + - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl + displayName: Run build.sh helix arm64 target + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + installNodeJs: false + artifacts: + - name: Helix_arm64_logs + path: artifacts/log/ + publishOnError: true + includeForks: true + # Source build - job: Source_Build displayName: 'Test: Linux Source Build' diff --git a/.azure/pipelines/helix-test.yml b/.azure/pipelines/helix-test.yml index ed10e8fc2c..ad17b9963e 100644 --- a/.azure/pipelines/helix-test.yml +++ b/.azure/pipelines/helix-test.yml @@ -25,23 +25,3 @@ jobs: - name: Helix_logs path: artifacts/log/ publishOnError: true - -# Build Helix ARM64 -- template: jobs/default-build.yml - parameters: - jobName: Helix_arm64 - jobDisplayName: "Tests: Helix ARM64" - agentOs: Linux - timeoutInMinutes: 240 - steps: - - script: ./restore.sh -ci - displayName: Restore - - script: ./build.sh -ci --arch arm64 -test --no-build-nodejs -projects $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true -bl - displayName: Run build.sh helix arm64 target - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops - installNodeJs: false - artifacts: - - name: Helix_arm64_logs - path: artifacts/logs/ - publishOnError: true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 86a87beb59..3acc059820 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,7 @@ /src/Hosting/ @tratcher @anurse /src/Http/ @tratcher @jkotalik @anurse /src/Middleware/ @tratcher @anurse -/src/ProjectTemplates/ @ryanbrandenburg +# /src/ProjectTemplates/ @ryanbrandenburg /src/Security/ @tratcher @anurse /src/Servers/ @tratcher @jkotalik @anurse @halter73 /src/Middleware/Rewrite @jkotalik @anurse diff --git a/Directory.Build.props b/Directory.Build.props index 650a9d7340..2ef491a409 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -52,7 +52,7 @@ true - netcoreapp3.1 + netcoreapp5.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index cac579c3bc..2ad7d9287e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,408 +9,408 @@ --> - + https://github.com/aspnet/Blazor - 348e050ecd9bd8924581afb677089ae5e2d5e508 + f5d0da88db7b29207fc005a7478bf0b9b6fc9fc9 - + https://github.com/aspnet/AspNetCore-Tooling - 9dc38f98bd6eb330aa1463c38bb2f6c6eccdb309 + 01db467df0350147bbf04f80f8c16033c23a4b4a - + https://github.com/aspnet/AspNetCore-Tooling - 9dc38f98bd6eb330aa1463c38bb2f6c6eccdb309 + 01db467df0350147bbf04f80f8c16033c23a4b4a - + https://github.com/aspnet/AspNetCore-Tooling - 9dc38f98bd6eb330aa1463c38bb2f6c6eccdb309 + 01db467df0350147bbf04f80f8c16033c23a4b4a - + https://github.com/aspnet/AspNetCore-Tooling - 9dc38f98bd6eb330aa1463c38bb2f6c6eccdb309 + 01db467df0350147bbf04f80f8c16033c23a4b4a - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/EntityFrameworkCore - 6cfdd4a9df8fa46ece6894541278b01b6411d273 + 423238a9c032044ade98f0723bf1113ef1e23949 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/dotnet/core-setup - b0ea03ec3af2a9c388f718fbb4978984ab339953 + 9042fe6c81aa3b47f58ccd94ff02e42f9f7a4e46 - + https://github.com/dotnet/core-setup - b0ea03ec3af2a9c388f718fbb4978984ab339953 + 9042fe6c81aa3b47f58ccd94ff02e42f9f7a4e46 - + https://github.com/dotnet/core-setup - b0ea03ec3af2a9c388f718fbb4978984ab339953 + 9042fe6c81aa3b47f58ccd94ff02e42f9f7a4e46 - + https://github.com/dotnet/core-setup - b0ea03ec3af2a9c388f718fbb4978984ab339953 + 9042fe6c81aa3b47f58ccd94ff02e42f9f7a4e46 - + https://github.com/dotnet/corefx - d0f0bfa5b123b4c1183c889cf4017bb529675502 + be3d4bad4576eecda116d3e9a368cd6959ecf5ce - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 https://github.com/dotnet/arcade @@ -424,13 +424,13 @@ https://github.com/dotnet/arcade f8546fbab59a74a66c83b8cb76b3f6877ce1d374 - + https://github.com/aspnet/Extensions - 8b0d85434b8da04ab6395496803525db6003fa22 + d15c5687db29e4e1f31a302fe243226b0a3a17e3 - + https://github.com/dotnet/roslyn - cac1be1463d3b277184ed38115ae35b0cb236688 + 3c865821f2864393a0ff7fe22c92ded6d51a546c diff --git a/eng/Versions.props b/eng/Versions.props index 26cf85de83..e111c8a406 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,8 +6,8 @@ --> - 3 - 1 + 5 + 0 0 1 false release - true - false - preview$(PreReleasePreviewNumber) - Preview $(PreReleasePreviewNumber) + alpha$(PreReleasePreviewNumber) + Alpha $(PreReleasePreviewNumber) $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) false @@ -59,114 +57,114 @@ 1.0.0-beta.19462.4 - 3.4.0-beta2-19462-08 + 3.4.0-beta1-19456-03 - 3.1.0-preview1.19470.5 - 3.1.0-preview1.19470.5 - 3.1.0-preview1.19470.5 - 2.1.0-preview1.19470.5 + 5.0.0-alpha1.19465.2 + 5.0.0-alpha1.19465.2 + 5.0.0-alpha1.19465.2 + 2.1.0-alpha1.19465.2 - 1.1.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 1.8.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 - 4.7.0-preview1.19463.3 + 1.2.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 1.9.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 + 5.0.0-alpha1.19462.7 - 3.1.0-preview1.19463.3 + 5.0.0-alpha1.19462.7 - 3.0.0-preview9.19462.2 + 5.0.0-alpha1.19463.2 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 - 3.1.0-preview1.19470.1 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 + 5.0.0-alpha1.19467.2 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 - 3.1.0-preview1.19471.7 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 + 5.0.0-alpha1.19468.19 - 3.1.0-preview1.19472.1 - 3.1.0-preview1.19472.1 - 3.1.0-preview1.19472.1 - 3.1.0-preview1.19472.1 + 5.0.0-alpha1.19467.1 + 5.0.0-alpha1.19467.1 + 5.0.0-alpha1.19467.1 + 5.0.0-alpha1.19467.1 - + - 3.1 + 5.0 @@ -25,11 +25,6 @@ - - - $(MicrosoftNETCorePlatformsPackageVersion) - - nul' + $changedFiles = & cmd /c 'git --no-pager diff --ignore-space-change --name-only 2>nul' # Temporary: Disable check for blazor js file $changedFilesExclusion = "src/Components/Web.JS/dist/Release/blazor.server.js" @@ -177,10 +177,9 @@ try { if ($changedFiles) { foreach ($file in $changedFiles) { if ($file -eq $changedFilesExclusion) {continue} - $filePath = Resolve-Path "${repoRoot}/${file}" LogError "Generated code is not up to date in $file. You might need to regenerate the reference assemblies or project list (see docs/ReferenceAssemblies.md and docs/ReferenceResolution.md)" -filepath $filePath - & git --no-pager diff --ignore-space-at-eol $filePath + & git --no-pager diff --ignore-space-change $filePath } } } diff --git a/eng/targets/Helix.Common.props b/eng/targets/Helix.Common.props index 8ea26294a2..d35d51c8b0 100644 --- a/eng/targets/Helix.Common.props +++ b/eng/targets/Helix.Common.props @@ -18,7 +18,6 @@ - diff --git a/eng/targets/Npm.Common.targets b/eng/targets/Npm.Common.targets index 062a9d3a8f..7695ed7039 100644 --- a/eng/targets/Npm.Common.targets +++ b/eng/targets/Npm.Common.targets @@ -20,16 +20,20 @@ - - - + + + + + + + - - + + @@ -53,12 +57,25 @@ + + + + + + + + + DependsOnTargets="GetBuildInputCacheFile" + Inputs="@(TSFiles);$(BaseIntermediateOutputPath)tsfiles.cache" + Outputs="@(BuildOutputFiles)"> - + @@ -73,7 +90,10 @@ - + + <_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName) diff --git a/eng/tools/BaselineGenerator/BaselineGenerator.csproj b/eng/tools/BaselineGenerator/BaselineGenerator.csproj index 143b3063d7..082a9eaf02 100644 --- a/eng/tools/BaselineGenerator/BaselineGenerator.csproj +++ b/eng/tools/BaselineGenerator/BaselineGenerator.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + $(DefaultNetCoreTargetFramework) -s https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json $(MSBuildThisFileDirectory)../../ diff --git a/eng/tools/RepoTasks/RepoTasks.csproj b/eng/tools/RepoTasks/RepoTasks.csproj index a954207698..5bef776106 100644 --- a/eng/tools/RepoTasks/RepoTasks.csproj +++ b/eng/tools/RepoTasks/RepoTasks.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + $(DefaultNetCoreTargetFramework) $(TargetFrameworks);net472 $(DefineConstants);BUILD_MSI_TASKS false @@ -16,7 +16,7 @@ - + diff --git a/eng/tools/RepoTasks/RepoTasks.tasks b/eng/tools/RepoTasks/RepoTasks.tasks index 5fcc97d156..0fa015d81f 100644 --- a/eng/tools/RepoTasks/RepoTasks.tasks +++ b/eng/tools/RepoTasks/RepoTasks.tasks @@ -1,6 +1,6 @@ - <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp3.1 + <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp5.0 <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' != 'core'">net472 <_RepoTaskAssembly>$(ArtifactsBinDir)RepoTasks\Release\$(_RepoTaskAssemblyFolder)\RepoTasks.dll diff --git a/global.json b/global.json index 41b9856784..1ff988fe32 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "3.1.100-preview1-014024" + "version": "5.0.100-alpha1-013788" }, "tools": { - "dotnet": "3.1.100-preview1-014024", + "dotnet": "5.0.100-alpha1-013788", "runtimes": { "dotnet/x64": [ "$(MicrosoftNETCoreAppRuntimeVersion)" diff --git a/src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs b/src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs similarity index 93% rename from src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs rename to src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs index 92e2252304..e478f1ef45 100644 --- a/src/Components/Analyzers/test/ComponentInternalUsageDiagnoticsAnalyzerTest.cs +++ b/src/Components/Analyzers/test/ComponentInternalUsageDiagnosticsAnalyzerTest.cs @@ -8,9 +8,9 @@ using Xunit; namespace Microsoft.AspNetCore.Components.Analyzers { - public class ComponentInternalUsageDiagnoticsAnalyzerTest : AnalyzerTestBase + public class ComponentInternalUsageDiagnosticsAnalyzerTest : AnalyzerTestBase { - public ComponentInternalUsageDiagnoticsAnalyzerTest() + public ComponentInternalUsageDiagnosticsAnalyzerTest() { Analyzer = new ComponentInternalUsageDiagnosticAnalyzer(); Runner = new ComponentAnalyzerDiagnosticAnalyzerRunner(Analyzer); diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs similarity index 83% rename from src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs rename to src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs index 415030a011..453cd69d74 100644 --- a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs +++ b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameAsParameter.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Components.RenderTree; -namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnoticsAnalyzerTest +namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnosticsAnalyzerTest { class UsesRenderTreeFrameAsParameter { diff --git a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs similarity index 86% rename from src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs rename to src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs index bdd40c2df1..daf857f996 100644 --- a/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnoticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs +++ b/src/Components/Analyzers/test/TestFiles/ComponentInternalUsageDiagnosticsAnalyzerTest/UsesRenderTreeFrameTypeAsLocal.cs @@ -1,7 +1,7 @@ using System; using Microsoft.AspNetCore.Components.RenderTree; -namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnoticsAnalyzerTest +namespace Microsoft.AspNetCore.Components.Analyzers.Tests.TestFiles.ComponentInternalUsageDiagnosticsAnalyzerTest { class UsesRenderTreeFrameTypeAsLocal { diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs index d08162a590..5182a1660d 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public static void Run(this IWebAssemblyHost host) { // Behave like async void, because we don't yet support async-main properly on WebAssembly. - // However, don't actualy make this method async, because we rely on startup being synchronous + // However, don't actually make this method async, because we rely on startup being synchronous // for things like attaching navigation event handlers. host.StartAsync().ContinueWith(task => { diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs index c86c1cf30b..1769dbd915 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs +++ b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs @@ -20,6 +20,11 @@ namespace Microsoft.AspNetCore.Blazor.Services public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + if (!IsEnabled(logLevel)) + { + return; + } + var formattedMessage = formatter(state, exception); Console.WriteLine($"[{logLevel}] {formattedMessage}"); } diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index 96a844817e..42ef903f15 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -436,9 +436,15 @@ + + + + <_MonoLinkerDotNetPath>$(DOTNET_HOST_PATH) + <_MonoLinkerDotNetPath Condition="'$(_MonoLinkerDotNetPath)' == ''">dotnet + - + diff --git a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs index d15cf4f584..9e924cb7b1 100644 --- a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs @@ -96,7 +96,7 @@ namespace Test } [Fact] - public void Render_ChildComponent_TriesToSetNonParamter() + public void Render_ChildComponent_TriesToSetNonParameter() { // Arrange AdditionalSyntaxTrees.Add(Parse(@" diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs index 348b69c691..e6df317bd9 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -100,7 +100,7 @@ namespace WsProxy { break; } case "Debugger.paused": { - //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack + //TODO figure out how to stitch out more frames and, in particular what happens when real wasm is on the stack var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value (); if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") { await OnBreakPointHit (args, token); @@ -419,7 +419,7 @@ namespace WsProxy { var res = await SendCommand("Runtime.evaluate", o, token); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse(msg_id, res, token); @@ -475,7 +475,7 @@ namespace WsProxy { var res = await SendCommand ("Runtime.evaluate", o, token); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse (msg_id, res, token); return; @@ -594,7 +594,7 @@ namespace WsProxy { var res = await EnableBreakPoint (bp, token); var ret_code = res.Value? ["result"]? ["value"]?.Value (); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (!ret_code.HasValue) { //FIXME figure out how to inform the IDE of that. Info ($"FAILED TO ENABLE BP {bp.LocalId}"); @@ -668,7 +668,7 @@ namespace WsProxy { var res = await EnableBreakPoint (bp, token); var ret_code = res.Value? ["result"]? ["value"]?.Value (); - //if we fail we just buble that to the IDE (and let it panic over it) + //if we fail we just bubble that to the IDE (and let it panic over it) if (!ret_code.HasValue) { SendResponse (msg_id, res, token); return; diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json index 33e094e356..119303324e 100644 --- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json +++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json @@ -83,12 +83,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index f77689bde2..30ded5fe73 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -430,7 +430,7 @@ namespace Microsoft.AspNetCore.Components private static string FormatEnumValueCore(T value, CultureInfo culture) where T : struct, Enum { - return value.ToString(); // The overload that acccepts a culture is [Obsolete] + return value.ToString(); // The overload that accepts a culture is [Obsolete] } private static string FormatNullableEnumValueCore(T? value, CultureInfo culture) where T : struct, Enum @@ -440,7 +440,7 @@ namespace Microsoft.AspNetCore.Components return null; } - return value.Value.ToString(); // The overload that acccepts a culture is [Obsolete] + return value.Value.ToString(); // The overload that accepts a culture is [Obsolete] } /// @@ -1166,99 +1166,99 @@ namespace Microsoft.AspNetCore.Components public static BindFormatter Get() { - if (!_cache.TryGetValue(typeof(T), out var formattter)) + if (!_cache.TryGetValue(typeof(T), out var formatter)) { // 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; + formatter = (BindFormatter)FormatStringValueCore; } else if (typeof(T) == typeof(bool)) { - formattter = (BindFormatter)FormatBoolValueCore; + formatter = (BindFormatter)FormatBoolValueCore; } else if (typeof(T) == typeof(bool?)) { - formattter = (BindFormatter)FormatNullableBoolValueCore; + formatter = (BindFormatter)FormatNullableBoolValueCore; } else if (typeof(T) == typeof(int)) { - formattter = (BindFormatter)FormatIntValueCore; + formatter = (BindFormatter)FormatIntValueCore; } else if (typeof(T) == typeof(int?)) { - formattter = (BindFormatter)FormatNullableIntValueCore; + formatter = (BindFormatter)FormatNullableIntValueCore; } else if (typeof(T) == typeof(long)) { - formattter = (BindFormatter)FormatLongValueCore; + formatter = (BindFormatter)FormatLongValueCore; } else if (typeof(T) == typeof(long?)) { - formattter = (BindFormatter)FormatNullableLongValueCore; + formatter = (BindFormatter)FormatNullableLongValueCore; } else if (typeof(T) == typeof(float)) { - formattter = (BindFormatter)FormatFloatValueCore; + formatter = (BindFormatter)FormatFloatValueCore; } else if (typeof(T) == typeof(float?)) { - formattter = (BindFormatter)FormatNullableFloatValueCore; + formatter = (BindFormatter)FormatNullableFloatValueCore; } else if (typeof(T) == typeof(double)) { - formattter = (BindFormatter)FormatDoubleValueCore; + formatter = (BindFormatter)FormatDoubleValueCore; } else if (typeof(T) == typeof(double?)) { - formattter = (BindFormatter)FormatNullableDoubleValueCore; + formatter = (BindFormatter)FormatNullableDoubleValueCore; } else if (typeof(T) == typeof(decimal)) { - formattter = (BindFormatter)FormatDecimalValueCore; + formatter = (BindFormatter)FormatDecimalValueCore; } else if (typeof(T) == typeof(decimal?)) { - formattter = (BindFormatter)FormatNullableDecimalValueCore; + formatter = (BindFormatter)FormatNullableDecimalValueCore; } else if (typeof(T) == typeof(DateTime)) { - formattter = (BindFormatter)FormatDateTimeValueCore; + formatter = (BindFormatter)FormatDateTimeValueCore; } else if (typeof(T) == typeof(DateTime?)) { - formattter = (BindFormatter)FormatNullableDateTimeValueCore; + formatter = (BindFormatter)FormatNullableDateTimeValueCore; } else if (typeof(T) == typeof(DateTimeOffset)) { - formattter = (BindFormatter)FormatDateTimeOffsetValueCore; + formatter = (BindFormatter)FormatDateTimeOffsetValueCore; } else if (typeof(T) == typeof(DateTimeOffset?)) { - formattter = (BindFormatter)FormatNullableDateTimeOffsetValueCore; + formatter = (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); + formatter = 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); + formatter = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindFormatter), target: null); } else { - formattter = MakeTypeConverterFormatter(); + formatter = MakeTypeConverterFormatter(); } - _cache.TryAdd(typeof(T), formattter); + _cache.TryAdd(typeof(T), formatter); } - return (BindFormatter)formattter; + return (BindFormatter)formatter; } private static BindFormatter MakeTypeConverterFormatter() diff --git a/src/Components/Components/src/CascadingValue.cs b/src/Components/Components/src/CascadingValue.cs index 564ca99def..8e0de1536c 100644 --- a/src/Components/Components/src/CascadingValue.cs +++ b/src/Components/Components/src/CascadingValue.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Components _hasSetParametersPreviously = true; - // It's OK for the value to be null, but some "Value" param must be suppled + // It's OK for the value to be null, but some "Value" param must be supplied // because it serves no useful purpose to have a otherwise. if (!hasSuppliedValue) { diff --git a/src/Components/Components/src/ComponentBase.cs b/src/Components/Components/src/ComponentBase.cs index 51c95f386b..d4b38db345 100644 --- a/src/Components/Components/src/ComponentBase.cs +++ b/src/Components/Components/src/ComponentBase.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Components // about IComponent). This gives us flexibility to change the lifecycle concepts easily, // or for developers to design their own lifecycles as different base classes. - // TODO: When the component lifecycle design stabilises, add proper unit tests for ComponentBase. + // TODO: When the component lifecycle design stabilizes, add proper unit tests for ComponentBase. /// /// Optional base class for components. Alternatively, components may @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Components /// /// /// The and lifecycle methods - /// are useful for performing interop, or interacting with values recieved from @ref. + /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Components /// A representing any asynchronous operation. /// /// The and lifecycle methods - /// are useful for performing interop, or interacting with values recieved from @ref. + /// are useful for performing interop, or interacting with values received from @ref. /// Use the parameter to ensure that initialization work is only performed /// once. /// @@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Components } catch // avoiding exception filters for AOT runtime support { - // Ignore exceptions from task cancelletions. + // Ignore exceptions from task cancellations. // Awaiting a canceled task may produce either an OperationCanceledException (if produced as a consequence of // CancellationToken.ThrowIfCancellationRequested()) or a TaskCanceledException (produced as a consequence of awaiting Task.FromCanceled). // It's much easier to check the state of the Task (i.e. Task.IsCanceled) rather than catch two distinct exceptions. @@ -289,7 +289,7 @@ namespace Microsoft.AspNetCore.Components } catch // avoiding exception filters for AOT runtime support { - // Ignore exceptions from task cancelletions, but don't bother issuing a state change. + // Ignore exceptions from task cancellations, but don't bother issuing a state change. if (task.IsCanceled) { return; diff --git a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs index 99eac1965a..2a27533dfb 100644 --- a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs +++ b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs @@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.Components /// // // NOTE: for number parsing, the HTML5 spec dictates that the DOM will represent - // number values as floating point numbers using `.` as the period separator. This is NOT culture senstive. + // number values as floating point numbers using `.` as the period separator. This is NOT culture sensitive. // Put another way, the user might see `,` as their decimal separator, but the value available in events - // to JS code is always simpilar to what .NET parses with InvariantCulture. + // to JS code is always similar to what .NET parses with InvariantCulture. // // See: https://www.w3.org/TR/html5/sec-forms.html#number-state-typenumber // See: https://www.w3.org/TR/html5/infrastructure.html#valid-floating-point-number diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec index 3c4fee8be5..b7c428d87e 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec @@ -9,7 +9,7 @@ - + diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec index 80f61e7f17..8e45bbdac0 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp.nuspec @@ -3,7 +3,7 @@ $CommonMetadataElements$ - + diff --git a/src/Components/Components/src/NavigationManager.cs b/src/Components/Components/src/NavigationManager.cs index d75077026f..0ad565fb54 100644 --- a/src/Components/Components/src/NavigationManager.cs +++ b/src/Components/Components/src/NavigationManager.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Components.Routing; namespace Microsoft.AspNetCore.Components { /// - /// Provides an abstraction for querying and mananging URI navigation. + /// Provides an abstraction for querying and managing URI navigation. /// public abstract class NavigationManager { @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Components } /// - /// Allows derived classes to lazyly self-initialize. Implementations that support lazy-initialization should override + /// Allows derived classes to lazily self-initialize. Implementations that support lazy-initialization should override /// this method and call . /// protected virtual void EnsureInitialized() diff --git a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs index e73119e038..1e6249c8d7 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeFrameType.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree public enum RenderTreeFrameType: short { /// - /// Used only for unintialized frames. + /// Used only for uninitialized frames. /// None = 0, diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 05cfb41abe..09470cebda 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree // remaining work. // During the synchronous rendering process we don't wait for the pending asynchronous // work to finish as it will simply trigger new renders that will be handled afterwards. - // During the asynchronous rendering process we want to wait up untill al components have + // During the asynchronous rendering process we want to wait up until all components have // finished rendering so that we can produce the complete output. var componentState = GetRequiredComponentState(componentId); componentState.SetDirectParameters(initialParameters); @@ -388,7 +388,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree : null; /// - /// Processses pending renders requests from components if there are any. + /// Processes pending renders requests from components if there are any. /// protected virtual void ProcessPendingRender() { diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 6876c97f0d..d648cdf5bc 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Components.Rendering AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { - // Since this is a component, we need to preserve the type of the EventCallabck, so we have + // Since this is a component, we need to preserve the type of the EventCallback, so we have // to box. Append(RenderTreeFrame.Attribute(sequence, name, (object)value)); } diff --git a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs index 176e4d83e4..d25d50b6de 100644 --- a/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs +++ b/src/Components/Components/src/Rendering/RendererSynchronizationContext.cs @@ -147,20 +147,20 @@ namespace Microsoft.AspNetCore.Components.Rendering // synchronously runs the callback public override void Send(SendOrPostCallback d, object state) { - Task antecedant; + Task antecedent; var completion = new TaskCompletionSource(); lock (_state.Lock) { - antecedant = _state.Task; + antecedent = _state.Task; _state.Task = completion.Task; } // We have to block. That's the contract of Send - we don't expect this to be used // in many scenarios in Components. // - // Using Wait here is ok because the antecedant task will never throw. - antecedant.Wait(); + // Using Wait here is ok because the antecedent task will never throw. + antecedent.Wait(); ExecuteSynchronously(completion, d, state); } @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Components.Rendering ExecuteSynchronously(completion, d, state); } - private Task Enqueue(Task antecedant, SendOrPostCallback d, object state, bool forceAsync = false) + private Task Enqueue(Task antecedent, SendOrPostCallback d, object state, bool forceAsync = false) { // If we get here is means that a callback is being explicitly queued. Let's instead add it to the queue and yield. // @@ -212,7 +212,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } var flags = forceAsync ? TaskContinuationOptions.RunContinuationsAsynchronously : TaskContinuationOptions.None; - return antecedant.ContinueWith(BackgroundWorkThunk, new WorkItem() + return antecedent.ContinueWith(BackgroundWorkThunk, new WorkItem() { SynchronizationContext = this, ExecutionContext = executionContext, diff --git a/src/Components/Components/test/CascadingParameterTest.cs b/src/Components/Components/test/CascadingParameterTest.cs index aba1199d9b..a78dea1163 100644 --- a/src/Components/Components/test/CascadingParameterTest.cs +++ b/src/Components/Components/test/CascadingParameterTest.cs @@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act/Assert 2: Re-render the CascadingValue; observe nested component wasn't re-rendered providedValue = "Updated value"; - displayNestedComponent = false; // Remove the nested componet + displayNestedComponent = false; // Remove the nested component component.TriggerRender(); // Assert: We did not render the nested component now it's been removed diff --git a/src/Components/Components/test/ParameterViewTest.Assignment.cs b/src/Components/Components/test/ParameterViewTest.Assignment.cs index 9f677ef50a..e2629a70e5 100644 --- a/src/Components/Components/test/ParameterViewTest.Assignment.cs +++ b/src/Components/Components/test/ParameterViewTest.Assignment.cs @@ -366,7 +366,7 @@ namespace Microsoft.AspNetCore.Components public void HasDuplicateCaptureUnmatchedValuesParameters_Throws() { // Arrange - var target = new HasDupliateCaptureUnmatchedValuesProperty(); + var target = new HasDuplicateCaptureUnmatchedValuesProperty(); var parameters = new ParameterViewBuilder().Build(); // Act @@ -374,17 +374,17 @@ namespace Microsoft.AspNetCore.Components // Assert Assert.Equal( - $"Multiple properties were found on component type '{typeof(HasDupliateCaptureUnmatchedValuesProperty).FullName}' " + + $"Multiple properties were found on component type '{typeof(HasDuplicateCaptureUnmatchedValuesProperty).FullName}' " + $"with '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. " + $"Only a single property per type can use '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. " + $"Properties:" + Environment.NewLine + - $"{nameof(HasDupliateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp1)}" + Environment.NewLine + - $"{nameof(HasDupliateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp2)}", + $"{nameof(HasDuplicateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp1)}" + Environment.NewLine + + $"{nameof(HasDuplicateCaptureUnmatchedValuesProperty.CaptureUnmatchedValuesProp2)}", ex.Message); } [Fact] - public void HasCaptureUnmatchedValuesParameteterWithWrongType_Throws() + public void HasCaptureUnmatchedValuesParameterWithWrongType_Throws() { // Arrange var target = new HasWrongTypeCaptureUnmatchedValuesProperty(); @@ -630,7 +630,7 @@ namespace Microsoft.AspNetCore.Components [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary CaptureUnmatchedValues { get; set; } } - class HasDupliateCaptureUnmatchedValuesProperty + class HasDuplicateCaptureUnmatchedValuesProperty { [Parameter(CaptureUnmatchedValues = true)] public Dictionary CaptureUnmatchedValuesProp1 { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IDictionary CaptureUnmatchedValuesProp2 { get; set; } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index 28f617eba2..9f3ba71809 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -442,7 +442,7 @@ namespace Microsoft.AspNetCore.Components.Test [Fact] public void HandlesKeyBeingAdded() { - // This is an anomolous situation that can't occur with .razor components. + // This is an anomalous situation that can't occur with .razor components. // It represents the case where, for the same sequence number, we have an // old frame without a key and a new frame with a key. @@ -472,7 +472,7 @@ namespace Microsoft.AspNetCore.Components.Test [Fact] public void HandlesKeyBeingRemoved() { - // This is an anomolous situation that can't occur with .razor components. + // This is an anomalous situation that can't occur with .razor components. // It represents the case where, for the same sequence number, we have an // old frame with a key and a new frame without a key. diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index a48cda6939..db78ba5079 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -3574,7 +3574,7 @@ namespace Microsoft.AspNetCore.Components.Test // Act &A Assert renderer.Dispose(); - // All components must be disposed even if some throw as part of being diposed. + // All components must be disposed even if some throw as part of being disposed. Assert.True(component.Disposed); var aex = Assert.IsType(Assert.Single(renderer.HandledExceptions)); Assert.Contains(exception1, aex.InnerExceptions); diff --git a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs index 568d2501bb..c92a585bd6 100644 --- a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs +++ b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } [Fact] - public void Post_RunsAynchronously_WhenNotBusy_Exception() + public void Post_RunsAsynchronously_WhenNotBusy_Exception() { // Arrange var context = new RendererSynchronizationContext(); diff --git a/src/Components/Server/src/CircuitDisconnectMiddleware.cs b/src/Components/Server/src/CircuitDisconnectMiddleware.cs index d64c31e7da..ac5cf4c6cc 100644 --- a/src/Components/Server/src/CircuitDisconnectMiddleware.cs +++ b/src/Components/Server/src/CircuitDisconnectMiddleware.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Server { - // We use a middlware so that we can use DI. + // We use a middleware so that we can use DI. internal class CircuitDisconnectMiddleware { private const string CircuitIdKey = "circuitId"; diff --git a/src/Components/Server/src/CircuitOptions.cs b/src/Components/Server/src/CircuitOptions.cs index 68ca25c85a..9f862b069d 100644 --- a/src/Components/Server/src/CircuitOptions.cs +++ b/src/Components/Server/src/CircuitOptions.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Components.Server /// without losing any state in the event of transient connection issues. /// /// - /// This value determines the maximium number of circuit states retained by the server. + /// This value determines the maximum number of circuit states retained by the server. /// /// /// @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.Server /// without losing any state in the event of transient connection issues. /// /// - /// This value determines the maximium duration circuit state is retained by the server before being evicted. + /// This value determines the maximum duration circuit state is retained by the server before being evicted. /// /// /// diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 0d7c7336dd..39021348bb 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -355,7 +355,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // EndInvokeJSFromDotNet is used in a fire-and-forget context, so it's responsible for its own // error handling. - public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeded, string arguments) + public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeeded, string arguments) { AssertInitialized(); AssertNotDisposed(); @@ -364,7 +364,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - if (!succeded) + if (!succeeded) { // We can log the arguments here because it is simply the JS error with the call stack. Log.EndInvokeJSFailed(_logger, asyncCall, arguments); @@ -577,11 +577,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private static class Log { - private static readonly Action _intializationStarted; - private static readonly Action _intializationSucceded; - private static readonly Action _intializationFailed; + private static readonly Action _initializationStarted; + private static readonly Action _initializationSucceded; + private static readonly Action _initializationFailed; private static readonly Action _disposeStarted; - private static readonly Action _disposeSucceded; + private static readonly Action _disposeSucceeded; private static readonly Action _disposeFailed; private static readonly Action _onCircuitOpened; private static readonly Action _onConnectionUp; @@ -639,7 +639,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static readonly EventId EndInvokeJSSucceeded = new EventId(206, "EndInvokeJSSucceeded"); public static readonly EventId DispatchEventThroughJSInterop = new EventId(207, "DispatchEventThroughJSInterop"); public static readonly EventId LocationChange = new EventId(208, "LocationChange"); - public static readonly EventId LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded"); + public static readonly EventId LocationChangeSucceeded = new EventId(209, "LocationChangeSucceeded"); public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed"); public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit"); public static readonly EventId OnRenderCompletedFailed = new EventId(212, "OnRenderCompletedFailed"); @@ -647,17 +647,17 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits static Log() { - _intializationStarted = LoggerMessage.Define( + _initializationStarted = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationStarted, "Circuit initialization started."); - _intializationSucceded = LoggerMessage.Define( + _initializationSucceded = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationSucceeded, "Circuit initialization succeeded."); - _intializationFailed = LoggerMessage.Define( + _initializationFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationFailed, "Circuit initialization failed."); @@ -667,10 +667,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits EventIds.DisposeStarted, "Disposing circuit '{CircuitId}' started."); - _disposeSucceded = LoggerMessage.Define( + _disposeSucceeded = LoggerMessage.Define( LogLevel.Debug, EventIds.DisposeSucceeded, - "Disposing circuit '{CircuitId}' succeded."); + "Disposing circuit '{CircuitId}' succeeded."); _disposeFailed = LoggerMessage.Define( LogLevel.Debug, @@ -725,7 +725,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _unhandledExceptionClientDisconnected = LoggerMessage.Define( LogLevel.Debug, EventIds.UnhandledExceptionClientDisconnected, - "An exception ocurred on the circuit host '{CircuitId}' while the client is disconnected."); + "An exception occurred on the circuit host '{CircuitId}' while the client is disconnected."); _beginInvokeDotNetStatic = LoggerMessage.Define( LogLevel.Debug, @@ -779,8 +779,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _locationChangeSucceeded = LoggerMessage.Define( LogLevel.Debug, - EventIds.LocationChangeSucceded, - "Location change to '{URI}' in circuit '{CircuitId}' succeded."); + EventIds.LocationChangeSucceeded, + "Location change to '{URI}' in circuit '{CircuitId}' succeeded."); _locationChangeFailed = LoggerMessage.Define( LogLevel.Debug, @@ -798,11 +798,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits "Failed to complete render batch '{RenderId}' in circuit host '{CircuitId}'."); } - public static void InitializationStarted(ILogger logger) => _intializationStarted(logger, null); - public static void InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null); - public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception); + public static void InitializationStarted(ILogger logger) => _initializationStarted(logger, null); + public static void InitializationSucceeded(ILogger logger) => _initializationSucceded(logger, null); + public static void InitializationFailed(ILogger logger, Exception exception) => _initializationFailed(logger, exception); public static void DisposeStarted(ILogger logger, CircuitId circuitId) => _disposeStarted(logger, circuitId, null); - public static void DisposeSucceeded(ILogger logger, CircuitId circuitId) => _disposeSucceded(logger, circuitId, null); + public static void DisposeSucceeded(ILogger logger, CircuitId circuitId) => _disposeSucceeded(logger, circuitId, null); public static void DisposeFailed(ILogger logger, CircuitId circuitId, Exception exception) => _disposeFailed(logger, circuitId, exception); public static void CircuitOpened(ILogger logger, CircuitId circuitId) => _onCircuitOpened(logger, circuitId, null); public static void ConnectionUp(ILogger logger, CircuitId circuitId, string connectionId) => _onConnectionUp(logger, circuitId, connectionId, null); diff --git a/src/Components/Server/src/Circuits/CircuitRegistry.cs b/src/Components/Server/src/Circuits/CircuitRegistry.cs index ab261a108b..f8e4971a64 100644 --- a/src/Components/Server/src/Circuits/CircuitRegistry.cs +++ b/src/Components/Server/src/Circuits/CircuitRegistry.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits /// the to use the new client instance that attempted to reconnect to the server. Removing the entry from /// should ensure we no longer have to concern ourselves with entry expiration. /// - /// Knowing when a client disconnected is not an exact science. There's a fair possiblity that a client may reconnect before the server realizes. + /// Knowing when a client disconnected is not an exact science. There's a fair possibility that a client may reconnect before the server realizes. /// Consequently, we have to account for reconnects and disconnects occuring simultaneously as well as appearing out of order. /// To manage this, we use a critical section to manage all state transitions. /// @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits else { // DisconnectCore may fail to disconnect the circuit if it was previously marked inactive or - // has been transfered to a new connection. Do not invoke the circuit handlers in this instance. + // has been transferred to a new connection. Do not invoke the circuit handlers in this instance. // We have to do in this instance. return Task.CompletedTask; @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Transition the host from disconnected to connected if it's available. In this critical section, we return // an existing host if it's currently considered connected or transition a disconnected host to connected. - // Transfering also wires up the client to the new set. + // Transferring also wires up the client to the new set. (circuitHost, previouslyConnected) = ConnectCore(circuitId, clientProxy, connectionId); if (circuitHost == null) @@ -428,7 +428,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _connectingToDisconnectedCircuit = LoggerMessage.Define( LogLevel.Debug, EventIds.ConnectingToDisconnectedCircuit, - "Transfering disconnected circuit {CircuitId} to connection {ConnectionId}."); + "Transferring disconnected circuit {CircuitId} to connection {ConnectionId}."); _failedToReconnectToCircuit = LoggerMessage.Define( LogLevel.Debug, diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 3596ffbcc6..0a977b34cf 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -72,14 +72,14 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // as we have a client that is not acknowledging render batches fast enough (something we consider needs // to be fast). // The result is something as follows: - // Lets imagine an extreme case where the server produces a new batch every milisecond. - // Lets say the client is able to ACK a batch every 100 miliseconds. + // Lets imagine an extreme case where the server produces a new batch every millisecond. + // Lets say the client is able to ACK a batch every 100 milliseconds. // When the app starts the client might see the sequence 0->(MaxUnacknowledgedRenderBatches-1) and then - // after 100 miliseconds it sees it jump to 1xx, then to 2xx where xx is something between {0..99} the + // after 100 milliseconds it sees it jump to 1xx, then to 2xx where xx is something between {0..99} the // reason for this is that the server slows down rendering new batches to as fast as the client can consume // them. // Similarly, if a client were to send events at a faster pace than the server can consume them, the server - // would still proces the events, but would not produce new renders until it gets an ack that frees up space + // would still process the events, but would not produce new renders until it gets an ack that frees up space // for a new render. // We should never see UnacknowledgedRenderBatches.Count > _options.MaxBufferedUnacknowledgedRenderBatches @@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { // Send the render batch to the client // If the "send" operation fails (synchronously or asynchronously) or the client - // gets disconected simply give up. This likely means that + // gets disconnected simply give up. This likely means that // the circuit went offline while sending the data, so simply wait until the // client reconnects back or the circuit gets evicted because it stayed // disconnected for too long. @@ -229,7 +229,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // from the client that it has received and successfully applied all batches up to that point). // If receive an ack for a previously acknowledged batch, its an error, as the messages are - // guranteed to be delivered in order, so a message for a render batch of 2 will never arrive + // guaranteed to be delivered in order, so a message for a render batch of 2 will never arrive // after a message for a render batch for 3. // If that were to be the case, it would just be enough to relax the checks here and simply skip // the message. @@ -264,7 +264,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits if (lastBatchId < incomingBatchId) { - // This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and + // This exception is due to a bad client input, so we mark it as such to prevent logging it as a warning and // flooding the logs with warnings. throw new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'."); } diff --git a/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs b/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs index 35bc454416..588eadfdd8 100644 --- a/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs +++ b/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } [Fact] - public void CreateCircuitId_Generates_GeneratesDifferentIds_ForSuccesiveCalls() + public void CreateCircuitId_Generates_GeneratesDifferentIds_ForSuccessiveCalls() { // Arrange var factory = TestCircuitIdFactory.CreateTestFactory(); diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index e7cda62bb2..6befe0c65d 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -179,7 +179,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering // Assert Assert.Equal(new long[] { 2, 3, 4 }, renderIds); - Assert.True(task.Wait(3000), "One or more render batches werent acknowledged"); + Assert.True(task.Wait(3000), "One or more render batches weren't acknowledged"); await task; } @@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering exceptions.Add(e); }; - // Receive the ack for the intial batch + // Receive the ack for the initial batch _ = renderer.OnRenderCompletedAsync(2, null); // Receive the ack for the second batch _ = renderer.OnRenderCompletedAsync(3, null); diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs index e0a6d8ff44..03a651d7be 100644 --- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs +++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs @@ -66,9 +66,9 @@ namespace Microsoft.AspNetCore.Components.Server.Tests services.AddServerSideBlazor(); services.AddSingleton(new ConfigurationBuilder().Build()); - var serviceProvder = services.BuildServiceProvider(); + var serviceProvider = services.BuildServiceProvider(); - return new ApplicationBuilder(serviceProvder); + return new ApplicationBuilder(serviceProvider); } private class MyComponent : IComponent diff --git a/src/Components/Shared/src/ElementReferenceJsonConverter.cs b/src/Components/Shared/src/ElementReferenceJsonConverter.cs index 465a688bf2..80a7738107 100644 --- a/src/Components/Shared/src/ElementReferenceJsonConverter.cs +++ b/src/Components/Shared/src/ElementReferenceJsonConverter.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Components } else { - throw new JsonException($"Unexcepted JSON Token {reader.TokenType}."); + throw new JsonException($"Unexpected JSON Token {reader.TokenType}."); } } @@ -49,4 +49,4 @@ namespace Microsoft.AspNetCore.Components writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj b/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj index 8e0a17ece0..cb890a62b8 100644 --- a/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj +++ b/src/Components/Web.JS/Microsoft.AspNetCore.Components.Web.JS.npmproj @@ -6,6 +6,11 @@ false + + + + + 0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch(function(e){})},e}(),d=function(){function e(e){this.minimumLogLevel=e,this.outputConsole=console}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.a.Critical:case r.a.Error:this.outputConsole.error("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Warning:this.outputConsole.warn("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Information:this.outputConsole.info("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;default:this.outputConsole.log("["+(new Date).toISOString()+"] "+r.a[e]+": "+t)}},e}()},function(e,t,n){"use strict";n.r(t);var r,o,i=n(3),s=n(4),a=n(43),c=n(0),u=(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),l=function(e){function t(t){var n=e.call(this)||this;return n.logger=t,n}return u(t,e),t.prototype.send=function(e){var t=this;return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new i.a):e.method?e.url?new Promise(function(n,r){var o=new XMLHttpRequest;o.open(e.method,e.url,!0),o.withCredentials=!0,o.setRequestHeader("X-Requested-With","XMLHttpRequest"),o.setRequestHeader("Content-Type","text/plain;charset=UTF-8");var a=e.headers;a&&Object.keys(a).forEach(function(e){o.setRequestHeader(e,a[e])}),e.responseType&&(o.responseType=e.responseType),e.abortSignal&&(e.abortSignal.onabort=function(){o.abort(),r(new i.a)}),e.timeout&&(o.timeout=e.timeout),o.onload=function(){e.abortSignal&&(e.abortSignal.onabort=null),o.status>=200&&o.status<300?n(new s.b(o.status,o.statusText,o.response||o.responseText)):r(new i.b(o.statusText,o.status))},o.onerror=function(){t.logger.log(c.a.Warning,"Error from HTTP request. "+o.status+": "+o.statusText+"."),r(new i.b(o.statusText,o.status))},o.ontimeout=function(){t.logger.log(c.a.Warning,"Timeout from HTTP request."),r(new i.c)},o.send(e.content||"")}):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(s.a),f=function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),h=function(e){function t(t){var n=e.call(this)||this;return"undefined"!=typeof XMLHttpRequest?n.httpClient=new l(t):n.httpClient=new a.a(t),n}return f(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new i.a):e.method?e.url?this.httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t.prototype.getCookieString=function(e){return this.httpClient.getCookieString(e)},t}(s.a),p=n(44);!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(o||(o={}));var d,g=n(1),y=function(){function e(){this.observers=[]}return e.prototype.next=function(e){for(var t=0,n=this.observers;t0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?[2,Promise.reject(new Error("Unable to connect to the server with any of the available transports. "+i.join(" ")))]:[2,Promise.reject(new Error("None of the transports supported by the client are supported by the server."))]}})})},e.prototype.constructTransport=function(e){switch(e){case E.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new A(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket);case E.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new D(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource);case E.LongPolling:return new P(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1);default:throw new Error("Unknown transport: "+e+".")}},e.prototype.startTransport=function(e,t){var n=this;return this.transport.onreceive=this.onreceive,this.transport.onclose=function(e){return n.stopConnection(e)},this.transport.connect(e,t)},e.prototype.resolveTransportOrError=function(e,t,n){var r=E[e.transport];if(null==r)return this.logger.log(c.a.Debug,"Skipping transport '"+e.transport+"' because it is not supported by this client."),new Error("Skipping transport '"+e.transport+"' because it is not supported by this client.");if(!function(e,t){return!e||0!=(t&e)}(t,r))return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it was disabled by the client."),new Error("'"+E[r]+"' is disabled by the client.");if(!(e.transferFormats.map(function(e){return S[e]}).indexOf(n)>=0))return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it does not support the requested transfer format '"+S[n]+"'."),new Error("'"+E[r]+"' does not support "+S[n]+".");if(r===E.WebSockets&&!this.options.WebSocket||r===E.ServerSentEvents&&!this.options.EventSource)return this.logger.log(c.a.Debug,"Skipping transport '"+E[r]+"' because it is not supported in your environment.'"),new Error("'"+E[r]+"' is not supported in your environment.");this.logger.log(c.a.Debug,"Selecting transport '"+E[r]+"'.");try{return this.constructTransport(r)}catch(e){return e}},e.prototype.isITransport=function(e){return e&&"object"==typeof e&&"connect"in e},e.prototype.stopConnection=function(e){if(this.logger.log(c.a.Debug,"HttpConnection.stopConnection("+e+") called while in state "+this.connectionState+"."),this.transport=void 0,e=this.stopError||e,this.stopError=void 0,"Disconnected"!==this.connectionState)if("Connecting "!==this.connectionState){if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(c.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(c.a.Information,"Connection disconnected."),this.connectionId=void 0,this.connectionState="Disconnected",this.onclose&&this.connectionStarted){this.connectionStarted=!1;try{this.onclose(e)}catch(t){this.logger.log(c.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(c.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection hasn't yet left the in the connecting state.");else this.logger.log(c.a.Debug,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is already in the disconnected state.")},e.prototype.resolveUrl=function(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!g.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(c.a.Information,"Normalizing '"+e+"' to '"+t.href+"'."),t.href},e.prototype.resolveNegotiateUrl=function(e){var t=e.indexOf("?"),n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",n+=-1===t?"":e.substring(t)},e}();var q=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new W,this.transportResult=new W,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new W),this.transportResult.promise},e.prototype.stop=function(){return this.executing=!1,this.sendBufferedData.resolve(),this.sendLoopPromise},e.prototype.bufferData=function(e){if(this.buffer.length&&typeof this.buffer[0]!=typeof e)throw new Error("Expected data to be of type "+typeof this.buffer+" but was of type "+typeof e);this.buffer.push(e),this.sendBufferedData.resolve()},e.prototype.sendLoop=function(){return B(this,void 0,void 0,function(){var t,n,r;return j(this,function(o){switch(o.label){case 0:return[4,this.sendBufferedData.promise];case 1:if(o.sent(),!this.executing)return this.transportResult&&this.transportResult.reject("Connection stopped."),[3,6];this.sendBufferedData=new W,t=this.transportResult,this.transportResult=void 0,n="string"==typeof this.buffer[0]?this.buffer.join(""):e.concatBuffers(this.buffer),this.buffer.length=0,o.label=2;case 2:return o.trys.push([2,4,,5]),[4,this.transport.send(n)];case 3:return o.sent(),t.resolve(),[3,5];case 4:return r=o.sent(),t.reject(r),[3,5];case 5:return[3,0];case 6:return[2]}})})},e.concatBuffers=function(e){for(var t=e.map(function(e){return e.byteLength}).reduce(function(e,t){return e+t}),n=new Uint8Array(t),r=0,o=0,i=e;o0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch(function(e){})},e}(),d=function(){function e(e){this.minimumLogLevel=e,this.outputConsole=console}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.a.Critical:case r.a.Error:this.outputConsole.error("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Warning:this.outputConsole.warn("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;case r.a.Information:this.outputConsole.info("["+(new Date).toISOString()+"] "+r.a[e]+": "+t);break;default:this.outputConsole.log("["+(new Date).toISOString()+"] "+r.a[e]+": "+t)}},e}()},function(e,t,n){"use strict";n.r(t);var r,o=n(3),i=n(4),a=n(0),s=(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),c=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=200&&s.status<300?n(new i.b(s.status,s.statusText,s.response||s.responseText)):r(new o.b(s.statusText,s.status))},s.onerror=function(){t.logger.log(a.a.Warning,"Error from HTTP request. "+s.status+": "+s.statusText+"."),r(new o.b(s.statusText,s.status))},s.ontimeout=function(){t.logger.log(a.a.Warning,"Timeout from HTTP request."),r(new o.c)},s.send(e.content||"")}):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(i.a),y=function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),v=function(e){function t(t){var n=e.call(this)||this;return"undefined"!=typeof fetch?n.httpClient=new f(t):"undefined"!=typeof XMLHttpRequest?n.httpClient=new g(t):n.httpClient=new p.a(t),n}return y(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new o.a):e.method?e.url?this.httpClient.send(e):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t.prototype.getCookieString=function(e){return this.httpClient.getCookieString(e)},t}(i.a),b=n(44);!function(e){e[e.Invocation=1]="Invocation",e[e.StreamItem=2]="StreamItem",e[e.Completion=3]="Completion",e[e.StreamInvocation=4]="StreamInvocation",e[e.CancelInvocation=5]="CancelInvocation",e[e.Ping=6]="Ping",e[e.Close=7]="Close"}(h||(h={}));var m,w=n(1),E=function(){function e(){this.observers=[]}return e.prototype.next=function(e){for(var t=0,n=this.observers;t0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?[2,Promise.reject(new Error("Unable to connect to the server with any of the available transports. "+i.join(" ")))]:[2,Promise.reject(new Error("None of the transports supported by the client are supported by the server."))]}})})},e.prototype.constructTransport=function(e){switch(e){case I.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new F(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket);case I.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new j(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource);case I.LongPolling:return new M(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1);default:throw new Error("Unknown transport: "+e+".")}},e.prototype.startTransport=function(e,t){var n=this;return this.transport.onreceive=this.onreceive,this.transport.onclose=function(e){return n.stopConnection(e)},this.transport.connect(e,t)},e.prototype.resolveTransportOrError=function(e,t,n){var r=I[e.transport];if(null==r)return this.logger.log(a.a.Debug,"Skipping transport '"+e.transport+"' because it is not supported by this client."),new Error("Skipping transport '"+e.transport+"' because it is not supported by this client.");if(!function(e,t){return!e||0!=(t&e)}(t,r))return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it was disabled by the client."),new Error("'"+I[r]+"' is disabled by the client.");if(!(e.transferFormats.map(function(e){return k[e]}).indexOf(n)>=0))return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it does not support the requested transfer format '"+k[n]+"'."),new Error("'"+I[r]+"' does not support "+k[n]+".");if(r===I.WebSockets&&!this.options.WebSocket||r===I.ServerSentEvents&&!this.options.EventSource)return this.logger.log(a.a.Debug,"Skipping transport '"+I[r]+"' because it is not supported in your environment.'"),new Error("'"+I[r]+"' is not supported in your environment.");this.logger.log(a.a.Debug,"Selecting transport '"+I[r]+"'.");try{return this.constructTransport(r)}catch(e){return e}},e.prototype.isITransport=function(e){return e&&"object"==typeof e&&"connect"in e},e.prototype.stopConnection=function(e){if(this.logger.log(a.a.Debug,"HttpConnection.stopConnection("+e+") called while in state "+this.connectionState+"."),this.transport=void 0,e=this.stopError||e,this.stopError=void 0,"Disconnected"!==this.connectionState)if("Connecting "!==this.connectionState){if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(a.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(a.a.Information,"Connection disconnected."),this.connectionId=void 0,this.connectionState="Disconnected",this.onclose&&this.connectionStarted){this.connectionStarted=!1;try{this.onclose(e)}catch(t){this.logger.log(a.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(a.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection hasn't yet left the in the connecting state.");else this.logger.log(a.a.Debug,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is already in the disconnected state.")},e.prototype.resolveUrl=function(e){if(0===e.lastIndexOf("https://",0)||0===e.lastIndexOf("http://",0))return e;if(!w.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(a.a.Information,"Normalizing '"+e+"' to '"+t.href+"'."),t.href},e.prototype.resolveNegotiateUrl=function(e){var t=e.indexOf("?"),n=e.substring(0,-1===t?e.length:t);return"/"!==n[n.length-1]&&(n+="/"),n+="negotiate",n+=-1===t?"":e.substring(t)},e}();var K=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new X,this.transportResult=new X,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new X),this.transportResult.promise},e.prototype.stop=function(){return this.executing=!1,this.sendBufferedData.resolve(),this.sendLoopPromise},e.prototype.bufferData=function(e){if(this.buffer.length&&typeof this.buffer[0]!=typeof e)throw new Error("Expected data to be of type "+typeof this.buffer+" but was of type "+typeof e);this.buffer.push(e),this.sendBufferedData.resolve()},e.prototype.sendLoop=function(){return H(this,void 0,void 0,function(){var t,n,r;return q(this,function(o){switch(o.label){case 0:return[4,this.sendBufferedData.promise];case 1:if(o.sent(),!this.executing)return this.transportResult&&this.transportResult.reject("Connection stopped."),[3,6];this.sendBufferedData=new X,t=this.transportResult,this.transportResult=void 0,n="string"==typeof this.buffer[0]?this.buffer.join(""):e.concatBuffers(this.buffer),this.buffer.length=0,o.label=2;case 2:return o.trys.push([2,4,,5]),[4,this.transport.send(n)];case 3:return o.sent(),t.resolve(),[3,5];case 4:return r=o.sent(),t.reject(r),[3,5];case 5:return[3,0];case 6:return[2]}})})},e.concatBuffers=function(e){for(var t=e.map(function(e){return e.byteLength}).reduce(function(e,t){return e+t}),n=new Uint8Array(t),r=0,o=0,i=e;o * @license MIT */ -var r=n(50),o=n(51),i=n(52);function s(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(e,t){if(s()=s())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+s().toString(16)+" bytes");return 0|e}function d(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return H(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function g(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function y(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:v(e,t,n,r,o);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):v(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function v(e,t,n,r,o){var i,s=1,a=e.length,c=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;s=2,a/=2,c/=2,n/=2}function u(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(o){var l=-1;for(i=n;ia&&(n=a-c),i=n;i>=0;i--){for(var f=!0,h=0;ho&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var s=0;s>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function _(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function I(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:u>223?3:u>191?2:1;if(o+f<=n)switch(f){case 1:u<128&&(l=u);break;case 2:128==(192&(i=e[o+1]))&&(c=(31&u)<<6|63&i)>127&&(l=c);break;case 3:i=e[o+1],s=e[o+2],128==(192&i)&&128==(192&s)&&(c=(15&u)<<12|(63&i)<<6|63&s)>2047&&(c<55296||c>57343)&&(l=c);break;case 4:i=e[o+1],s=e[o+2],a=e[o+3],128==(192&i)&&128==(192&s)&&128==(192&a)&&(c=(15&u)<<18|(63&i)<<12|(63&s)<<6|63&a)>65535&&c<1114112&&(l=c)}null===l?(l=65533,f=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),o+=f}return function(e){var t=e.length;if(t<=T)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return P(this,t,n);case"utf8":case"utf-8":return I(this,t,n);case"ascii":return k(this,t,n);case"latin1":case"binary":return x(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},c.prototype.compare=function(e,t,n,r,o){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),s=(n>>>=0)-(t>>>=0),a=Math.min(i,s),u=this.slice(r,o),l=e.slice(t,n),f=0;fo)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return m(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return E(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var T=4096;function k(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function D(e,t,n,r,o,i){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function L(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function M(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function A(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function B(e,t,n,r,i){return i||A(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function j(e,t,n,r,i){return i||A(e,0,n,8),o.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},c.prototype.readUInt8=function(e,t){return t||O(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||O(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||O(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||O(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||O(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},c.prototype.readInt8=function(e,t){return t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||O(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){t||O(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||O(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||O(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||O(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||O(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||D(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},c.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);D(this,e,t,n,o-1,-o)}var i=0,s=1,a=0;for(this[t]=255&e;++i>0)-a&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);D(this,e,t,n,o-1,-o)}var i=n-1,s=1,a=0;for(this[t+i]=255&e;--i>=0&&(s*=256);)e<0&&0===a&&0!==this[t+i+1]&&(a=1),this[t+i]=(e/s>>0)-a&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return B(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return j(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return j(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!c.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(s+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function H(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function q(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(10))},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}()},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.write=function(t){return""+t+e.RecordSeparator},e.parse=function(t){if(t[t.length-1]!==e.RecordSeparator)throw new Error("Message is incomplete.");var n=t.split(e.RecordSeparator);return n.pop(),n},e.RecordSeparatorCode=30,e.RecordSeparator=String.fromCharCode(e.RecordSeparatorCode),e}()},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setPlatform=function(e){return t.platform=e,t.platform}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=n(23),o=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=f;var i=n(20);i.inherits=n(15);var s=n(36),a=n(41);i.inherits(f,s);for(var c=o(a.prototype),u=0;u=0,"must have a non-negative type"),o(s,"must have a decode function"),this.registerEncoder(function(e){return e instanceof t},function(t){var o=i(),s=r.allocUnsafe(1);return s.writeInt8(e,0),o.append(s),o.append(n(t)),o}),this.registerDecoder(e,s),this},registerEncoder:function(e,n){return o(e,"must have an encode function"),o(n,"must have an encode function"),t.push({check:e,encode:n}),this},registerDecoder:function(e,t){return o(e>=0,"must have a non-negative type"),o(t,"must have a decode function"),n.push({type:e,decode:t}),this},encoder:s.encoder,decoder:s.decoder,buffer:!0,type:"msgpack5",IncompleteBufferError:a.IncompleteBufferError}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=p("_blazorLogicalChildren"),o=p("_blazorLogicalParent"),i=p("_blazorLogicalEnd");function s(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function a(e,t,n){var i=e;if(e instanceof Comment&&(u(i)&&u(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(c(i))throw new Error("Not implemented: moving existing logical children");var s=u(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=c,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return u(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=u,t.permuteLogicalChildren=function(e,t){var n=u(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=c(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):h(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var s=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=s}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=l},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function s(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=s),i(o,s),s.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},s.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},s.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},s.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(t.LogLevel||(t.LogLevel={}))},function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,i=null;function s(e){t.push(e)}function a(e,t,n,r){var o=u();if(o.invokeDotNetFromJS){var i=JSON.stringify(r,g),s=o.invokeDotNetFromJS(e,t,n,i);return s?f(s):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function c(e,t,r,i){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var s=o++,a=new Promise(function(e,t){n[s]={resolve:e,reject:t}});try{var c=JSON.stringify(i,g);u().beginInvokeDotNetFromJS(s,e,t,r,c)}catch(e){l(s,!1,e)}return a}function u(){if(null!==i)return i;throw new Error("No .NET call dispatcher has been set.")}function l(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function h(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){i=e},e.attachReviver=s,e.invokeMethod=function(e,t){for(var n=[],r=2;r1)for(var n=1;n0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.length)&&(r=this.length),n>=this.length)return e||i.alloc(0);if(r<=0)return e||i.alloc(0);var o,s,a=!!e,c=this._offset(n),u=r-n,l=u,f=a&&t||0,h=c[1];if(0===n&&r==this.length){if(!a)return 1===this._bufs.length?this._bufs[0]:i.concat(this._bufs,this.length);for(s=0;s(o=this._bufs[s].length-h))){this._bufs[s].copy(e,f,h,h+l);break}this._bufs[s].copy(e,f,h),f+=o,l-=o,h&&(h=0)}return e},s.prototype.shallowSlice=function(e,t){e=e||0,t=t||this.length,e<0&&(e+=this.length),t<0&&(t+=this.length);var n=this._offset(e),r=this._offset(t),o=this._bufs.slice(n[0],r[0]+1);return 0==r[1]?o.pop():o[o.length-1]=o[o.length-1].slice(0,r[1]),0!=n[1]&&(o[0]=o[0].slice(n[1])),new s(o)},s.prototype.toString=function(e,t,n){return this.slice(t,n).toString(e)},s.prototype.consume=function(e){for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},s.prototype.duplicate=function(){for(var e=0,t=new s;e0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=i)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return e}}),c=r[n];n=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),d(n)?r.showHidden=n:n&&t._extend(r,n),b(r.showHidden)&&(r.showHidden=!1),b(r.depth)&&(r.depth=2),b(r.colors)&&(r.colors=!1),b(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=c),l(r,e,r.depth)}function c(e,t){var n=a.styles[t];return n?"["+a.colors[n][0]+"m"+e+"["+a.colors[n][1]+"m":e}function u(e,t){return e}function l(e,n,r){if(e.customInspect&&n&&C(n.inspect)&&n.inspect!==t.inspect&&(!n.constructor||n.constructor.prototype!==n)){var o=n.inspect(r,e);return v(o)||(o=l(e,o,r)),o}var i=function(e,t){if(b(t))return e.stylize("undefined","undefined");if(v(t)){var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string")}if(y(t))return e.stylize(""+t,"number");if(d(t))return e.stylize(""+t,"boolean");if(g(t))return e.stylize("null","null")}(e,n);if(i)return i;var s=Object.keys(n),a=function(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}(s);if(e.showHidden&&(s=Object.getOwnPropertyNames(n)),S(n)&&(s.indexOf("message")>=0||s.indexOf("description")>=0))return f(n);if(0===s.length){if(C(n)){var c=n.name?": "+n.name:"";return e.stylize("[Function"+c+"]","special")}if(m(n))return e.stylize(RegExp.prototype.toString.call(n),"regexp");if(E(n))return e.stylize(Date.prototype.toString.call(n),"date");if(S(n))return f(n)}var u,w="",_=!1,I=["{","}"];(p(n)&&(_=!0,I=["[","]"]),C(n))&&(w=" [Function"+(n.name?": "+n.name:"")+"]");return m(n)&&(w=" "+RegExp.prototype.toString.call(n)),E(n)&&(w=" "+Date.prototype.toUTCString.call(n)),S(n)&&(w=" "+f(n)),0!==s.length||_&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=_?function(e,t,n,r,o){for(var i=[],s=0,a=t.length;s=0&&0,e+t.replace(/\u001b\[\d\d?m/g,"").length+1},0)>60)return n[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1];return n[0]+t+" "+e.join(", ")+" "+n[1]}(u,w,I)):I[0]+w+I[1]}function f(e){return"["+Error.prototype.toString.call(e)+"]"}function h(e,t,n,r,o,i){var s,a,c;if((c=Object.getOwnPropertyDescriptor(t,o)||{value:t[o]}).get?a=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(a=e.stylize("[Setter]","special")),k(r,o)||(s="["+o+"]"),a||(e.seen.indexOf(c.value)<0?(a=g(n)?l(e,c.value,null):l(e,c.value,n-1)).indexOf("\n")>-1&&(a=i?a.split("\n").map(function(e){return" "+e}).join("\n").substr(2):"\n"+a.split("\n").map(function(e){return" "+e}).join("\n")):a=e.stylize("[Circular]","special")),b(s)){if(i&&o.match(/^\d+$/))return a;(s=JSON.stringify(""+o)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(s=s.substr(1,s.length-2),s=e.stylize(s,"name")):(s=s.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),s=e.stylize(s,"string"))}return s+": "+a}function p(e){return Array.isArray(e)}function d(e){return"boolean"==typeof e}function g(e){return null===e}function y(e){return"number"==typeof e}function v(e){return"string"==typeof e}function b(e){return void 0===e}function m(e){return w(e)&&"[object RegExp]"===_(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===_(e)}function S(e){return w(e)&&("[object Error]"===_(e)||e instanceof Error)}function C(e){return"function"==typeof e}function _(e){return Object.prototype.toString.call(e)}function I(e){return e<10?"0"+e.toString(10):e.toString(10)}t.debuglog=function(n){if(b(i)&&(i=e.env.NODE_DEBUG||""),n=n.toUpperCase(),!s[n])if(new RegExp("\\b"+n+"\\b","i").test(i)){var r=e.pid;s[n]=function(){var e=t.format.apply(t,arguments);console.error("%s %d: %s",n,r,e)}}else s[n]=function(){};return s[n]},t.inspect=a,a.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},a.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},t.isArray=p,t.isBoolean=d,t.isNull=g,t.isNullOrUndefined=function(e){return null==e},t.isNumber=y,t.isString=v,t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=b,t.isRegExp=m,t.isObject=w,t.isDate=E,t.isError=S,t.isFunction=C,t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=n(54);var T=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function k(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){var e,n;console.log("%s - %s",(e=new Date,n=[I(e.getHours()),I(e.getMinutes()),I(e.getSeconds())].join(":"),[e.getDate(),T[e.getMonth()],n].join(" ")),t.format.apply(t,arguments))},t.inherits=n(55),t._extend=function(e,t){if(!t||!w(t))return e;for(var n=Object.keys(t),r=n.length;r--;)e[n[r]]=t[n[r]];return e};var x="undefined"!=typeof Symbol?Symbol("util.promisify.custom"):void 0;function P(e,t){if(!e){var n=new Error("Promise was rejected with a falsy value");n.reason=e,e=n}return t(e)}t.promisify=function(e){if("function"!=typeof e)throw new TypeError('The "original" argument must be of type Function');if(x&&e[x]){var t;if("function"!=typeof(t=e[x]))throw new TypeError('The "util.promisify.custom" argument must be of type Function');return Object.defineProperty(t,x,{value:t,enumerable:!1,writable:!1,configurable:!0}),t}function t(){for(var t,n,r=new Promise(function(e,r){t=e,n=r}),o=[],i=0;i0?("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===u.prototype||(t=function(e){return u.from(e)}(t)),r?s.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):E(e,s,t,!0):s.ended?e.emit("error",new Error("stream.push() after EOF")):(s.reading=!1,s.decoder&&!n?(t=s.decoder.write(t),s.objectMode||0!==t.length?E(e,s,t,!1):T(e,s)):E(e,s,t,!1))):r||(s.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=S?e=S:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(p("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(I,e):I(e))}function I(e){p("emit readable"),e.emit("readable"),R(e)}function T(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(s===i.length?o+=i:o+=i.slice(0,e),0===(e-=s)){s===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(s));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=u.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,s=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,s),0===(e-=s)){s===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(s));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function D(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(L,t,e))}function L(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function M(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return p("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?D(this):_(this),null;if(0===(e=C(e,t))&&t.ended)return 0===t.length&&D(this),null;var r,o=t.needReadable;return p("need readable",o),(0===t.length||t.length-e0?O(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&D(this)),null!==r&&this.emit("data",r),r},m.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},m.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,p("pipe count=%d opts=%j",i.pipesCount,t);var c=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?l:m;function u(t,r){p("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,p("cleanup"),e.removeListener("close",v),e.removeListener("finish",b),e.removeListener("drain",f),e.removeListener("error",y),e.removeListener("unpipe",u),n.removeListener("end",l),n.removeListener("end",m),n.removeListener("data",g),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function l(){p("onend"),e.end()}i.endEmitted?o.nextTick(c):n.once("end",c),e.on("unpipe",u);var f=function(e){return function(){var t=e._readableState;p("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&a(e,"data")&&(t.flowing=!0,R(e))}}(n);e.on("drain",f);var h=!1;var d=!1;function g(t){p("ondata"),d=!1,!1!==e.write(t)||d||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==M(i.pipes,e))&&!h&&(p("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,d=!0),n.pause())}function y(t){p("onerror",t),m(),e.removeListener("error",y),0===a(e,"error")&&e.emit("error",t)}function v(){e.removeListener("finish",b),m()}function b(){p("onfinish"),e.removeListener("close",v),m()}function m(){p("unpipe"),n.unpipe(e)}return n.on("data",g),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?s(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",y),e.once("close",v),e.once("finish",b),e.emit("pipe",n),i.flowing||(p("pipe resume"),n.resume()),e},m.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n),this);if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i0&&s.length>o&&!s.warned){s.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=s.length,a=c,console&&console.warn&&console.warn(a)}return e}function f(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=function(){for(var e=[],t=0;t0&&(s=t[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var c=o[e];if(void 0===c)return!1;if("function"==typeof c)i(c,this,t);else{var u=c.length,l=d(c,u);for(n=0;n=0;i--)if(n[i]===t||n[i].listener===t){s=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},a.prototype.listeners=function(e){return h(this,e,!0)},a.prototype.rawListeners=function(e){return h(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},a.prototype.listenerCount=p,a.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(37).EventEmitter},function(e,t,n){"use strict";var r=n(23);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return i||s?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var r=n(61).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=u,t=4;break;case"utf8":this.fillLast=a,t=4;break;case"base64":this.text=l,this.end=f,t=3;break;default:return this.write=h,void(this.end=p)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function s(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function a(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function l(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";(function(t,r,o){var i=n(23);function s(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=b;var a,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;b.WritableState=v;var u=n(20);u.inherits=n(15);var l={deprecate:n(64)},f=n(38),h=n(14).Buffer,p=o.Uint8Array||function(){};var d,g=n(39);function y(){}function v(e,t){a=a||n(11),e=e||{};var r=t instanceof a;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,u=e.writableHighWaterMark,l=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(u||0===u)?u:l,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var f=!1===e.decodeStrings;this.decodeStrings=!f,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(_,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),_(e,t))}(e,n,r,t,o);else{var s=S(n);s||n.corked||n.bufferProcessing||!n.bufferedRequest||E(e,n),r?c(w,e,n,s,o):w(e,n,s,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new s(this)}function b(e){if(a=a||n(11),!(d.call(b,this)||this instanceof a))return new b(e);this._writableState=new v(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),f.call(this)}function m(e,t,n,r,o,i,s){t.writelen=r,t.writecb=s,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function w(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),_(e,t)}function E(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var a=0,c=!0;n;)o[a]=n,n.isBuf||(c=!1),n=n.next,a+=1;o.allBuffers=c,m(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new s(t),t.bufferedRequestCount=0}else{for(;n;){var u=n.chunk,l=n.encoding,f=n.callback;if(m(e,t,!1,t.objectMode?1:u.length,u,l,f),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function S(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),_(e,t)})}function _(e,t){var n=S(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(C,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}u.inherits(b,f),v.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(v.prototype,"buffer",{get:l.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(d=Function.prototype[Symbol.hasInstance],Object.defineProperty(b,Symbol.hasInstance,{value:function(e){return!!d.call(this,e)||this===b&&(e&&e._writableState instanceof v)}})):d=function(e){return e instanceof this},b.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},b.prototype.write=function(e,t,n){var r,o=this._writableState,s=!1,a=!o.objectMode&&(r=e,h.isBuffer(r)||r instanceof p);return a&&!h.isBuffer(e)&&(e=function(e){return h.from(e)}(e)),"function"==typeof t&&(n=t,t=null),a?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=y),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(a||function(e,t,n,r){var o=!0,s=!1;return null===n?s=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(s=new TypeError("Invalid non-string/buffer chunk")),s&&(e.emit("error",s),i.nextTick(r,s),o=!1),o}(this,o,e,n))&&(o.pendingcb++,s=function(e,t,n,r,o,i){if(!n){var s=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=h.from(t,n));return t}(t,r,o);r!==s&&(n=!0,o="buffer",r=s)}var a=t.objectMode?1:r.length;t.length+=a;var c=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(b.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),b.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},b.prototype._writev=null,b.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,_(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),b.prototype.destroy=g.destroy,b.prototype._undestroy=g.undestroy,b.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(19),n(62).setImmediate,n(10))},function(e,t,n){"use strict";e.exports=s;var r=n(11),o=n(20);function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length=200&&c.statusCode<300?r(new s.b(c.statusCode,c.statusMessage||"",u)):o(new i.b(c.statusMessage||"",c.statusCode||0))});t.abortSignal&&(t.abortSignal.onabort=function(){f.abort(),o(new i.a)})})},n.prototype.getCookieString=function(e){return this.cookieJar.getCookieString(e)},n}(s.a)}).call(this,n(6).Buffer)},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return i});var r=n(8),o=n(1),i=function(){function t(){}return t.prototype.writeHandshakeRequest=function(e){return r.a.write(JSON.stringify(e))},t.prototype.parseHandshakeResponse=function(t){var n,i;if(Object(o.g)(t)||void 0!==e&&t instanceof e){var s=new Uint8Array(t);if(-1===(c=s.indexOf(r.a.RecordSeparatorCode)))throw new Error("Message is incomplete.");var a=c+1;n=String.fromCharCode.apply(null,s.slice(0,a)),i=s.byteLength>a?s.slice(a).buffer:null}else{var c,u=t;if(-1===(c=u.indexOf(r.a.RecordSeparator)))throw new Error("Message is incomplete.");a=c+1;n=u.substring(0,a),i=u.length>a?u.substring(a):null}var l=r.a.parse(n),f=JSON.parse(l[0]);if(f.type)throw new Error("Expected a handshake response from the server.");return[i,f]},t}()}).call(this,n(6).Buffer)},,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function s(e){try{c(r.next(e))}catch(e){i(e)}}function a(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(s,a)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)s.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spread||function(){for(var e=[],t=0;t0?r-4:r,f=0;f>16&255,a[c++]=t>>8&255,a[c++]=255&t;2===s&&(t=o[e.charCodeAt(f)]<<2|o[e.charCodeAt(f+1)]>>4,a[c++]=255&t);1===s&&(t=o[e.charCodeAt(f)]<<10|o[e.charCodeAt(f+1)]<<4|o[e.charCodeAt(f+2)]>>2,a[c++]=t>>8&255,a[c++]=255&t);return a},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],s=0,a=n-o;sa?a:s+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,c=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,n){for(var o,i,s=[],a=t;a>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return s.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,s,a=8*o-r-1,c=(1<>1,l=-7,f=n?o-1:0,h=n?-1:1,p=e[t+f];for(f+=h,i=p&(1<<-l)-1,p>>=-l,l+=a;l>0;i=256*i+e[t+f],f+=h,l-=8);for(s=i&(1<<-l)-1,i>>=-l,l+=r;l>0;s=256*s+e[t+f],f+=h,l-=8);if(0===i)i=1-u;else{if(i===c)return s?NaN:1/0*(p?-1:1);s+=Math.pow(2,r),i-=u}return(p?-1:1)*s*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var s,a,c,u=8*i-o-1,l=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?0:i-1,d=r?1:-1,g=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=l):(s=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-s))<1&&(s--,c*=2),(t+=s+f>=1?h/c:h*Math.pow(2,1-f))*c>=2&&(s++,c/=2),s+f>=l?(a=0,s=l):s+f>=1?(a=(t*c-1)*Math.pow(2,o),s+=f):(a=t*Math.pow(2,f-1)*Math.pow(2,o),s=0));o>=8;e[n+p]=255&a,p+=d,a/=256,o-=8);for(s=s<0;e[n+p]=255&s,p+=d,s/=256,u-=8);e[n+p-d]|=128*g}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";(function(t){ +var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function d(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return H(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function g(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function y(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:v(e,t,n,r,o);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):v(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function v(e,t,n,r,o){var i,a=1,s=e.length,c=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,c/=2,n/=2}function u(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(o){var l=-1;for(i=n;is&&(n=s-c),i=n;i>=0;i--){for(var f=!0,h=0;ho&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function _(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function T(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:u>223?3:u>191?2:1;if(o+f<=n)switch(f){case 1:u<128&&(l=u);break;case 2:128==(192&(i=e[o+1]))&&(c=(31&u)<<6|63&i)>127&&(l=c);break;case 3:i=e[o+1],a=e[o+2],128==(192&i)&&128==(192&a)&&(c=(15&u)<<12|(63&i)<<6|63&a)>2047&&(c<55296||c>57343)&&(l=c);break;case 4:i=e[o+1],a=e[o+2],s=e[o+3],128==(192&i)&&128==(192&a)&&128==(192&s)&&(c=(15&u)<<18|(63&i)<<12|(63&a)<<6|63&s)>65535&&c<1114112&&(l=c)}null===l?(l=65533,f=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),o+=f}return function(e){var t=e.length;if(t<=I)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return P(this,t,n);case"utf8":case"utf-8":return T(this,t,n);case"ascii":return k(this,t,n);case"latin1":case"binary":return x(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",n=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},c.prototype.compare=function(e,t,n,r,o){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),a=(n>>>=0)-(t>>>=0),s=Math.min(i,a),u=this.slice(r,o),l=e.slice(t,n),f=0;fo)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return m(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return E(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var I=4096;function k(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function D(e,t,n,r,o,i){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function L(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function M(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function A(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function B(e,t,n,r,i){return i||A(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function j(e,t,n,r,i){return i||A(e,0,n,8),o.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},c.prototype.readUInt8=function(e,t){return t||O(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||O(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||O(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||O(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||O(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},c.prototype.readInt8=function(e,t){return t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||O(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){t||O(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||O(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||O(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||O(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||O(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||D(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},c.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);D(this,e,t,n,o-1,-o)}var i=0,a=1,s=0;for(this[t]=255&e;++i>0)-s&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);D(this,e,t,n,o-1,-o)}var i=n-1,a=1,s=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===s&&0!==this[t+i+1]&&(s=1),this[t+i]=(e/a>>0)-s&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):L(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):L(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||D(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return B(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return j(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return j(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!c.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function H(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(U,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function q(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(10))},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}()},function(e,t,n){"use strict";n.d(t,"a",function(){return r});var r=function(){function e(){}return e.write=function(t){return""+t+e.RecordSeparator},e.parse=function(t){if(t[t.length-1]!==e.RecordSeparator)throw new Error("Message is incomplete.");var n=t.split(e.RecordSeparator);return n.pop(),n},e.RecordSeparatorCode=30,e.RecordSeparator=String.fromCharCode(e.RecordSeparatorCode),e}()},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setPlatform=function(e){return t.platform=e,t.platform}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=n(23),o=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=f;var i=n(20);i.inherits=n(15);var a=n(36),s=n(41);i.inherits(f,a);for(var c=o(s.prototype),u=0;u=0,"must have a non-negative type"),o(a,"must have a decode function"),this.registerEncoder(function(e){return e instanceof t},function(t){var o=i(),a=r.allocUnsafe(1);return a.writeInt8(e,0),o.append(a),o.append(n(t)),o}),this.registerDecoder(e,a),this},registerEncoder:function(e,n){return o(e,"must have an encode function"),o(n,"must have an encode function"),t.push({check:e,encode:n}),this},registerDecoder:function(e,t){return o(e>=0,"must have a non-negative type"),o(t,"must have a decode function"),n.push({type:e,decode:t}),this},encoder:a.encoder,decoder:a.decoder,buffer:!0,type:"msgpack5",IncompleteBufferError:s.IncompleteBufferError}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=p("_blazorLogicalChildren"),o=p("_blazorLogicalParent"),i=p("_blazorLogicalEnd");function a(e,t){if(e.childNodes.length>0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return e[r]=[],e}function s(e,t,n){var i=e;if(e instanceof Comment&&(u(i)&&u(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(c(i))throw new Error("Not implemented: moving existing logical children");var a=u(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=c,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return u(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=u,t.permuteLogicalChildren=function(e,t){var n=u(e);t.forEach(function(e){e.moveRangeStart=n[e.fromSiblingIndex],e.moveRangeEnd=function e(t){if(t instanceof Element)return t;var n=f(t);if(n)return n.previousSibling;var r=c(t);return r instanceof Element?r.lastChild:e(r)}(e.moveRangeStart)}),t.forEach(function(t){var r=t.moveToBeforeMarker=document.createComment("marker"),o=n[t.toSiblingIndex+1];o?o.parentNode.insertBefore(r,o):h(r,e)}),t.forEach(function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,i=r;i;){var a=i.nextSibling;if(n.insertBefore(i,t),i===o)break;i=a}n.removeChild(t)}),t.forEach(function(e){n[e.toSiblingIndex]=e.moveRangeStart})},t.getClosestDomElement=l},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.Trace=0]="Trace",e[e.Debug=1]="Debug",e[e.Information=2]="Information",e[e.Warning=3]="Warning",e[e.Error=4]="Error",e[e.Critical=5]="Critical",e[e.None=6]="None"}(t.LogLevel||(t.LogLevel={}))},function(e,t,n){"use strict";var r;!function(e){window.DotNet=e;var t=[],n={},r={},o=1,i=null;function a(e){t.push(e)}function s(e,t,n,r){var o=u();if(o.invokeDotNetFromJS){var i=JSON.stringify(r,g),a=o.invokeDotNetFromJS(e,t,n,i);return a?f(a):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function c(e,t,r,i){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var a=o++,s=new Promise(function(e,t){n[a]={resolve:e,reject:t}});try{var c=JSON.stringify(i,g);u().beginInvokeDotNetFromJS(a,e,t,r,c)}catch(e){l(a,!1,e)}return s}function u(){if(null!==i)return i;throw new Error("No .NET call dispatcher has been set.")}function l(e,t,r){if(!n.hasOwnProperty(e))throw new Error("There is no pending async call with ID "+e+".");var o=n[e];delete n[e],t?o.resolve(r):o.reject(r)}function f(e){return e?JSON.parse(e,function(e,n){return t.reduce(function(t,n){return n(e,t)},n)}):null}function h(e){return e instanceof Error?e.message+"\n"+e.stack:e?e.toString():"null"}function p(e){if(r.hasOwnProperty(e))return r[e];var t,n=window,o="window";if(e.split(".").forEach(function(e){if(!(e in n))throw new Error("Could not find '"+e+"' in '"+o+"'.");t=n,n=n[e],o+="."+e}),n instanceof Function)return n=n.bind(t),r[e]=n,n;throw new Error("The value '"+o+"' is not a function.")}e.attachDispatcher=function(e){i=e},e.attachReviver=a,e.invokeMethod=function(e,t){for(var n=[],r=2;r1)for(var n=1;n0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.length)&&(r=this.length),n>=this.length)return e||i.alloc(0);if(r<=0)return e||i.alloc(0);var o,a,s=!!e,c=this._offset(n),u=r-n,l=u,f=s&&t||0,h=c[1];if(0===n&&r==this.length){if(!s)return 1===this._bufs.length?this._bufs[0]:i.concat(this._bufs,this.length);for(a=0;a(o=this._bufs[a].length-h))){this._bufs[a].copy(e,f,h,h+l);break}this._bufs[a].copy(e,f,h),f+=o,l-=o,h&&(h=0)}return e},a.prototype.shallowSlice=function(e,t){e=e||0,t=t||this.length,e<0&&(e+=this.length),t<0&&(t+=this.length);var n=this._offset(e),r=this._offset(t),o=this._bufs.slice(n[0],r[0]+1);return 0==r[1]?o.pop():o[o.length-1]=o[o.length-1].slice(0,r[1]),0!=n[1]&&(o[0]=o[0].slice(n[1])),new a(o)},a.prototype.toString=function(e,t,n){return this.slice(t,n).toString(e)},a.prototype.consume=function(e){for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},a.prototype.duplicate=function(){for(var e=0,t=new a;e0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=i)return e;switch(e){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return e}}),c=r[n];n=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),d(n)?r.showHidden=n:n&&t._extend(r,n),b(r.showHidden)&&(r.showHidden=!1),b(r.depth)&&(r.depth=2),b(r.colors)&&(r.colors=!1),b(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=c),l(r,e,r.depth)}function c(e,t){var n=s.styles[t];return n?"["+s.colors[n][0]+"m"+e+"["+s.colors[n][1]+"m":e}function u(e,t){return e}function l(e,n,r){if(e.customInspect&&n&&C(n.inspect)&&n.inspect!==t.inspect&&(!n.constructor||n.constructor.prototype!==n)){var o=n.inspect(r,e);return v(o)||(o=l(e,o,r)),o}var i=function(e,t){if(b(t))return e.stylize("undefined","undefined");if(v(t)){var n="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(n,"string")}if(y(t))return e.stylize(""+t,"number");if(d(t))return e.stylize(""+t,"boolean");if(g(t))return e.stylize("null","null")}(e,n);if(i)return i;var a=Object.keys(n),s=function(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}(a);if(e.showHidden&&(a=Object.getOwnPropertyNames(n)),S(n)&&(a.indexOf("message")>=0||a.indexOf("description")>=0))return f(n);if(0===a.length){if(C(n)){var c=n.name?": "+n.name:"";return e.stylize("[Function"+c+"]","special")}if(m(n))return e.stylize(RegExp.prototype.toString.call(n),"regexp");if(E(n))return e.stylize(Date.prototype.toString.call(n),"date");if(S(n))return f(n)}var u,w="",_=!1,T=["{","}"];(p(n)&&(_=!0,T=["[","]"]),C(n))&&(w=" [Function"+(n.name?": "+n.name:"")+"]");return m(n)&&(w=" "+RegExp.prototype.toString.call(n)),E(n)&&(w=" "+Date.prototype.toUTCString.call(n)),S(n)&&(w=" "+f(n)),0!==a.length||_&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=_?function(e,t,n,r,o){for(var i=[],a=0,s=t.length;a=0&&0,e+t.replace(/\u001b\[\d\d?m/g,"").length+1},0)>60)return n[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+n[1];return n[0]+t+" "+e.join(", ")+" "+n[1]}(u,w,T)):T[0]+w+T[1]}function f(e){return"["+Error.prototype.toString.call(e)+"]"}function h(e,t,n,r,o,i){var a,s,c;if((c=Object.getOwnPropertyDescriptor(t,o)||{value:t[o]}).get?s=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(s=e.stylize("[Setter]","special")),k(r,o)||(a="["+o+"]"),s||(e.seen.indexOf(c.value)<0?(s=g(n)?l(e,c.value,null):l(e,c.value,n-1)).indexOf("\n")>-1&&(s=i?s.split("\n").map(function(e){return" "+e}).join("\n").substr(2):"\n"+s.split("\n").map(function(e){return" "+e}).join("\n")):s=e.stylize("[Circular]","special")),b(a)){if(i&&o.match(/^\d+$/))return s;(a=JSON.stringify(""+o)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=e.stylize(a,"name")):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=e.stylize(a,"string"))}return a+": "+s}function p(e){return Array.isArray(e)}function d(e){return"boolean"==typeof e}function g(e){return null===e}function y(e){return"number"==typeof e}function v(e){return"string"==typeof e}function b(e){return void 0===e}function m(e){return w(e)&&"[object RegExp]"===_(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===_(e)}function S(e){return w(e)&&("[object Error]"===_(e)||e instanceof Error)}function C(e){return"function"==typeof e}function _(e){return Object.prototype.toString.call(e)}function T(e){return e<10?"0"+e.toString(10):e.toString(10)}t.debuglog=function(n){if(b(i)&&(i=e.env.NODE_DEBUG||""),n=n.toUpperCase(),!a[n])if(new RegExp("\\b"+n+"\\b","i").test(i)){var r=e.pid;a[n]=function(){var e=t.format.apply(t,arguments);console.error("%s %d: %s",n,r,e)}}else a[n]=function(){};return a[n]},t.inspect=s,s.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},s.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},t.isArray=p,t.isBoolean=d,t.isNull=g,t.isNullOrUndefined=function(e){return null==e},t.isNumber=y,t.isString=v,t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=b,t.isRegExp=m,t.isObject=w,t.isDate=E,t.isError=S,t.isFunction=C,t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=n(54);var I=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function k(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){var e,n;console.log("%s - %s",(e=new Date,n=[T(e.getHours()),T(e.getMinutes()),T(e.getSeconds())].join(":"),[e.getDate(),I[e.getMonth()],n].join(" ")),t.format.apply(t,arguments))},t.inherits=n(55),t._extend=function(e,t){if(!t||!w(t))return e;for(var n=Object.keys(t),r=n.length;r--;)e[n[r]]=t[n[r]];return e};var x="undefined"!=typeof Symbol?Symbol("util.promisify.custom"):void 0;function P(e,t){if(!e){var n=new Error("Promise was rejected with a falsy value");n.reason=e,e=n}return t(e)}t.promisify=function(e){if("function"!=typeof e)throw new TypeError('The "original" argument must be of type Function');if(x&&e[x]){var t;if("function"!=typeof(t=e[x]))throw new TypeError('The "util.promisify.custom" argument must be of type Function');return Object.defineProperty(t,x,{value:t,enumerable:!1,writable:!1,configurable:!0}),t}function t(){for(var t,n,r=new Promise(function(e,r){t=e,n=r}),o=[],i=0;i0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===u.prototype||(t=function(e){return u.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):E(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?E(e,a,t,!1):I(e,a)):E(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=S?e=S:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(p("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(T,e):T(e))}function T(e){p("emit readable"),e.emit("readable"),R(e)}function I(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=u.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function D(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(L,t,e))}function L(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function M(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return p("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?D(this):_(this),null;if(0===(e=C(e,t))&&t.ended)return 0===t.length&&D(this),null;var r,o=t.needReadable;return p("need readable",o),(0===t.length||t.length-e0?O(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&D(this)),null!==r&&this.emit("data",r),r},m.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},m.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,p("pipe count=%d opts=%j",i.pipesCount,t);var c=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?l:m;function u(t,r){p("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,p("cleanup"),e.removeListener("close",v),e.removeListener("finish",b),e.removeListener("drain",f),e.removeListener("error",y),e.removeListener("unpipe",u),n.removeListener("end",l),n.removeListener("end",m),n.removeListener("data",g),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||f())}function l(){p("onend"),e.end()}i.endEmitted?o.nextTick(c):n.once("end",c),e.on("unpipe",u);var f=function(e){return function(){var t=e._readableState;p("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&s(e,"data")&&(t.flowing=!0,R(e))}}(n);e.on("drain",f);var h=!1;var d=!1;function g(t){p("ondata"),d=!1,!1!==e.write(t)||d||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==M(i.pipes,e))&&!h&&(p("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,d=!0),n.pause())}function y(t){p("onerror",t),m(),e.removeListener("error",y),0===s(e,"error")&&e.emit("error",t)}function v(){e.removeListener("finish",b),m()}function b(){p("onfinish"),e.removeListener("close",v),m()}function m(){p("unpipe"),n.unpipe(e)}return n.on("data",g),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",y),e.once("close",v),e.once("finish",b),e.emit("pipe",n),i.flowing||(p("pipe resume"),n.resume()),e},m.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n),this);if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i0&&a.length>o&&!a.warned){a.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=a.length,s=c,console&&console.warn&&console.warn(s)}return e}function f(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=function(){for(var e=[],t=0;t0&&(a=t[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var c=o[e];if(void 0===c)return!1;if("function"==typeof c)i(c,this,t);else{var u=c.length,l=d(c,u);for(n=0;n=0;i--)if(n[i]===t||n[i].listener===t){a=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(37).EventEmitter},function(e,t,n){"use strict";var r=n(23);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return i||a?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var r=n(61).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=u,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=l,this.end=f,t=3;break;default:return this.write=h,void(this.end=p)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function l(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";(function(t,r,o){var i=n(23);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=b;var s,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;b.WritableState=v;var u=n(20);u.inherits=n(15);var l={deprecate:n(64)},f=n(38),h=n(14).Buffer,p=o.Uint8Array||function(){};var d,g=n(39);function y(){}function v(e,t){s=s||n(11),e=e||{};var r=t instanceof s;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,u=e.writableHighWaterMark,l=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(u||0===u)?u:l,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var f=!1===e.decodeStrings;this.decodeStrings=!f,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(_,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),_(e,t))}(e,n,r,t,o);else{var a=S(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||E(e,n),r?c(w,e,n,a,o):w(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function b(e){if(s=s||n(11),!(d.call(b,this)||this instanceof s))return new b(e);this._writableState=new v(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),f.call(this)}function m(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function w(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),_(e,t)}function E(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var s=0,c=!0;n;)o[s]=n,n.isBuf||(c=!1),n=n.next,s+=1;o.allBuffers=c,m(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var u=n.chunk,l=n.encoding,f=n.callback;if(m(e,t,!1,t.objectMode?1:u.length,u,l,f),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function S(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function C(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),_(e,t)})}function _(e,t){var n=S(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(C,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}u.inherits(b,f),v.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(v.prototype,"buffer",{get:l.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(d=Function.prototype[Symbol.hasInstance],Object.defineProperty(b,Symbol.hasInstance,{value:function(e){return!!d.call(this,e)||this===b&&(e&&e._writableState instanceof v)}})):d=function(e){return e instanceof this},b.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},b.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,s=!o.objectMode&&(r=e,h.isBuffer(r)||r instanceof p);return s&&!h.isBuffer(e)&&(e=function(e){return h.from(e)}(e)),"function"==typeof t&&(n=t,t=null),s?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=y),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(s||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=h.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var s=t.objectMode?1:r.length;t.length+=s;var c=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(b.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),b.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},b.prototype._writev=null,b.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,_(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),b.prototype.destroy=g.destroy,b.prototype._undestroy=g.undestroy,b.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(19),n(62).setImmediate,n(10))},function(e,t,n){"use strict";e.exports=a;var r=n(11),o=n(20);function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length=200&&c.statusCode<300?r(new a.b(c.statusCode,c.statusMessage||"",u)):o(new i.b(c.statusMessage||"",c.statusCode||0))});t.abortSignal&&(t.abortSignal.onabort=function(){f.abort(),o(new i.a)})})},n.prototype.getCookieString=function(e){return this.cookieJar.getCookieString(e)},n}(a.a)}).call(this,n(6).Buffer)},function(e,t,n){"use strict";(function(e){n.d(t,"a",function(){return i});var r=n(8),o=n(1),i=function(){function t(){}return t.prototype.writeHandshakeRequest=function(e){return r.a.write(JSON.stringify(e))},t.prototype.parseHandshakeResponse=function(t){var n,i;if(Object(o.g)(t)||void 0!==e&&t instanceof e){var a=new Uint8Array(t);if(-1===(c=a.indexOf(r.a.RecordSeparatorCode)))throw new Error("Message is incomplete.");var s=c+1;n=String.fromCharCode.apply(null,a.slice(0,s)),i=a.byteLength>s?a.slice(s).buffer:null}else{var c,u=t;if(-1===(c=u.indexOf(r.a.RecordSeparator)))throw new Error("Message is incomplete.");s=c+1;n=u.substring(0,s),i=u.length>s?u.substring(s):null}var l=r.a.parse(n),f=JSON.parse(l[0]);if(f.type)throw new Error("Expected a handshake response from the server.");return[i,f]},t}()}).call(this,n(6).Buffer)},,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(r=i.next()).done;)a.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a},a=this&&this.__spread||function(){for(var e=[],t=0;t0?r-4:r,f=0;f>16&255,s[c++]=t>>8&255,s[c++]=255&t;2===a&&(t=o[e.charCodeAt(f)]<<2|o[e.charCodeAt(f+1)]>>4,s[c++]=255&t);1===a&&(t=o[e.charCodeAt(f)]<<10|o[e.charCodeAt(f+1)]<<4|o[e.charCodeAt(f+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t);return s},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;as?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,c=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,n){for(var o,i,a=[],s=t;s>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,c=(1<>1,l=-7,f=n?o-1:0,h=n?-1:1,p=e[t+f];for(f+=h,i=p&(1<<-l)-1,p>>=-l,l+=s;l>0;i=256*i+e[t+f],f+=h,l-=8);for(a=i&(1<<-l)-1,i>>=-l,l+=r;l>0;a=256*a+e[t+f],f+=h,l-=8);if(0===i)i=1-u;else{if(i===c)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,r),i-=u}return(p?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,c,u=8*i-o-1,l=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?0:i-1,d=r?1:-1,g=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=l):(a=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-a))<1&&(a--,c*=2),(t+=a+f>=1?h/c:h*Math.pow(2,1-f))*c>=2&&(a++,c/=2),a+f>=l?(s=0,a=l):a+f>=1?(s=(t*c-1)*Math.pow(2,o),a+=f):(s=t*Math.pow(2,f-1)*Math.pow(2,o),a=0));o>=8;e[n+p]=255&s,p+=d,s/=256,o-=8);for(a=a<0;e[n+p]=255&a,p+=d,a/=256,u-=8);e[n+p-d]|=128*g}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";(function(t){ /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh * @license MIT */ -function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,s))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var s="string"==typeof r,a=!e&&o&&!n;if((!e&&i.isError(o)&&s&&w(o,n)||a)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),s=o.indexOf("\n"+i);if(s>=0){var a=o.indexOf("\n",s+1);o=o.substring(a+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)s.call(e,n)&&t.push(n);return t}}).call(this,n(10))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(11)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(14).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),s=this.head,a=0;s;)t=s.data,n=i,o=a,t.copy(n,o),a+=s.data.length,s=s.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function s(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=s),i(o,s),s.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},s.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},s.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},s.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(10))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,s,a,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(s="setImmediate$"+Math.random()+"$",a=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(s)&&d(+t.data.slice(s.length))},e.addEventListener?e.addEventListener("message",a,!1):e.attachEvent("onmessage",a),r=function(t){e.postMessage(s+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=a},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(11),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(20);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(15),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(35).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var s=e.readUInt32BE(t+0),a=e.readUInt32BE(t+4);return(4294967296*s+a)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),s(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),s(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),a(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return s(e,r,i=15&h,1);if(128==(240&h))return a(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function s(e,t,r,o){var s,a=[],c=0;for(t+=o,s=0;si)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function a(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(a(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),s=1e6*(n-1e3*i);if(s||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var a=4*s,c=i/Math.pow(2,32),u=a+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,s=-1,a=[];for(n=0;n>8),a.push(255&s)):(a.push(201),a.push(s>>24),a.push(s>>16&255),a.push(s>>8&255),a.push(255&s));return o().append(r.from(a)).append(i)}(c)||function(e){var t,n,i=[],s=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++s,i.push(a(t,!0)),i.push(a(e[t],!0)));s<16?(n=r.allocUnsafe(1))[0]=128|s:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(s,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return s(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return s(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return s(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var s=1,a=t+7;a>=t;a--){var c=(255^e[a])+s;e[a]=255&c,s=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return a}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function s(e){try{c(r.next(e))}catch(e){i(e)}}function a(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(s,a)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(a.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(a.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(a.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new s.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(a.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(a.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,s=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new a(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=s;var a=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(16),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function s(e){try{c(r.next(e))}catch(e){i(e)}}function a(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(s,a)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=s},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(12),i=n(2),s=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+s))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+s):n.subarray(o+i,o+i+s)),o=o+i+s}return t},e}();var a=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],a=0,c=s.parse(e);a=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(10))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(11)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(14).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(6),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(10))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(11),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(20);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(15),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(35).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(16),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality.",this.message.querySelector("a").addEventListener("click",function(){return location.reload()})},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RejectedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RejectedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RejectedClassName="components-reconnect-rejected",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(6),o=n(12),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s - /// Formats the value as a string. Derived classes can override this to determine the formating used for . + /// Formats the value as a string. Derived classes can override this to determine the formatting used for . /// /// The value to format. /// A string representation of the value. diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index 4f0377ceed..7c8eec1a23 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Components.Forms } /// - /// Formats the value as a string. Derived classes can override this to determine the formating used for CurrentValueAsString. + /// Formats the value as a string. Derived classes can override this to determine the formatting used for CurrentValueAsString. /// /// The value to format. /// A string representation of the value. diff --git a/src/Components/Web/test/Forms/InputBaseTest.cs b/src/Components/Web/test/Forms/InputBaseTest.cs index 570b0f9283..9f43181465 100644 --- a/src/Components/Web/test/Forms/InputBaseTest.cs +++ b/src/Components/Web/test/Forms/InputBaseTest.cs @@ -212,7 +212,7 @@ namespace Microsoft.AspNetCore.Components.Forms }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); - // Act/Assert: Initally, it's valid and unmodified + // Act/Assert: Initially, it's valid and unmodified var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent); Assert.Equal("valid", inputComponent.CssClass); // no Class was specified diff --git a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs index ed045a6383..cdca53efaf 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests [Theory] [InlineData("en-US")] [InlineData("fr-FR")] - public void CanSetCultureAndParseCultueSensitiveNumbersAndDates(string culture) + public void CanSetCultureAndParseCultureSensitiveNumbersAndDates(string culture) { var cultureInfo = CultureInfo.GetCultureInfo(culture); SetCulture(culture); diff --git a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor index 13256fb7bc..77fbab5eb5 100644 --- a/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/ServerReliability/ReliabilityComponent.razor @@ -18,7 +18,7 @@

Error = @errorFailure

- + diff --git a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj index b2e913f352..40698c9d33 100644 --- a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj +++ b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj @@ -15,7 +15,7 @@
- + diff --git a/src/DataProtection/README.md b/src/DataProtection/README.md index cd58074d9e..4c558753b0 100644 --- a/src/DataProtection/README.md +++ b/src/DataProtection/README.md @@ -1,7 +1,7 @@ DataProtection ============== -Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/). +Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/aspnet/core/security/data-protection/). ## Community Maintained Data Protection Providers & Projects diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs index 79ef5dfc33..da51bb4a3c 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs @@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Tests var applicationName = "CreateDefaultBuilderApp"; var deploymentParameters = new DeploymentParameters(Path.Combine(GetTestSitesPath(), applicationName), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { - TargetFramework = "netcoreapp3.1", + TargetFramework = "netcoreapp5.0", HostingModel = HostingModel.InProcess }; @@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.Tests { var deploymentParameters = new DeploymentParameters(Path.Combine(GetTestSitesPath(), applicationName), ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { - TargetFramework = "netcoreapp3.1", + TargetFramework = "netcoreapp5.0", }; if (setTestEnvVars) diff --git a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj index 633be490c2..2761d442cf 100644 --- a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj +++ b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj @@ -25,7 +25,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant aspnetcore_base_runtime.version $(InstallersOutputPath)$(BaseRuntimeVersionFileName) - + runtimes/$(RuntimeIdentifier)/lib/ $(BuildOutputTargetFolder)$(DefaultNetCoreTargetFramework) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs index c64426c0db..84363221ba 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplication.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplication.cs @@ -114,6 +114,7 @@ namespace Microsoft.AspNetCore.Hosting public HttpContext HttpContext { get; set; } public IDisposable Scope { get; set; } public Activity Activity { get; set; } + internal HostingRequestStartingLog StartLog { get; set; } public long StartTimestamp { get; set; } internal bool HasDiagnosticListener { get; set; } @@ -125,6 +126,7 @@ namespace Microsoft.AspNetCore.Hosting Scope = null; Activity = null; + StartLog = null; StartTimestamp = 0; HasDiagnosticListener = false; diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 65730d67ff..385bcbf46d 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Hosting } // Non-inline - LogRequestStarting(httpContext); + LogRequestStarting(context); } } context.StartTimestamp = startTimestamp; @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Hosting { currentTimestamp = Stopwatch.GetTimestamp(); // Non-inline - LogRequestFinished(httpContext, startTimestamp, currentTimestamp); + LogRequestFinished(context, startTimestamp, currentTimestamp); } if (_diagnosticListener.IsEnabled()) @@ -167,30 +167,34 @@ namespace Microsoft.AspNetCore.Hosting } [MethodImpl(MethodImplOptions.NoInlining)] - private void LogRequestStarting(HttpContext httpContext) + private void LogRequestStarting(HostingApplication.Context context) { // IsEnabled is checked in the caller, so if we are here just log + var startLog = new HostingRequestStartingLog(context.HttpContext); + context.StartLog = startLog; + _logger.Log( logLevel: LogLevel.Information, eventId: LoggerEventIds.RequestStarting, - state: new HostingRequestStartingLog(httpContext), + state: startLog, exception: null, formatter: HostingRequestStartingLog.Callback); } [MethodImpl(MethodImplOptions.NoInlining)] - private void LogRequestFinished(HttpContext httpContext, long startTimestamp, long currentTimestamp) + private void LogRequestFinished(HostingApplication.Context context, long startTimestamp, long currentTimestamp) { // IsEnabled isn't checked in the caller, startTimestamp > 0 is used as a fast proxy check - // but that may be because diagnostics are enabled, which also uses startTimestamp, so check here - if (_logger.IsEnabled(LogLevel.Information)) + // but that may be because diagnostics are enabled, which also uses startTimestamp, + // so check if we logged the start event + if (context.StartLog != null) { var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); _logger.Log( logLevel: LogLevel.Information, eventId: LoggerEventIds.RequestFinished, - state: new HostingRequestFinishedLog(httpContext, elapsed), + state: new HostingRequestFinishedLog(context, elapsed), exception: null, formatter: HostingRequestFinishedLog.Callback); } diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs index 63fd5f0921..09a132851d 100644 --- a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs +++ b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs @@ -9,51 +9,56 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Hosting { + using static HostingRequestStartingLog; + internal class HostingRequestFinishedLog : IReadOnlyList> { internal static readonly Func Callback = (state, exception) => ((HostingRequestFinishedLog)state).ToString(); - private readonly HttpContext _httpContext; - private readonly TimeSpan _elapsed; + private readonly HostingApplication.Context _context; private string _cachedToString; + public TimeSpan Elapsed { get; } - public int Count => 3; + public int Count => 11; public KeyValuePair this[int index] { get { - switch (index) + var request = _context.HttpContext.Request; + var response = _context.HttpContext.Response; + + return index switch { - case 0: - return new KeyValuePair("ElapsedMilliseconds", _elapsed.TotalMilliseconds); - case 1: - return new KeyValuePair("StatusCode", _httpContext.Response.StatusCode); - case 2: - return new KeyValuePair("ContentType", _httpContext.Response.ContentType); - default: - throw new IndexOutOfRangeException(nameof(index)); - } + 0 => new KeyValuePair("ElapsedMilliseconds", Elapsed.TotalMilliseconds), + 1 => new KeyValuePair(nameof(response.StatusCode), response.StatusCode), + 2 => new KeyValuePair(nameof(response.ContentType), response.ContentType), + 3 => new KeyValuePair(nameof(response.ContentLength), response.ContentLength), + 4 => new KeyValuePair(nameof(request.Protocol), request.Protocol), + 5 => new KeyValuePair(nameof(request.Method), request.Method), + 6 => new KeyValuePair(nameof(request.Scheme), request.Scheme), + 7 => new KeyValuePair(nameof(request.Host), request.Host.Value), + 8 => new KeyValuePair(nameof(request.PathBase), request.PathBase.Value), + 9 => new KeyValuePair(nameof(request.Path), request.Path.Value), + 10 => new KeyValuePair(nameof(request.QueryString), request.QueryString.Value), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; } } - public HostingRequestFinishedLog(HttpContext httpContext, TimeSpan elapsed) + public HostingRequestFinishedLog(HostingApplication.Context context, TimeSpan elapsed) { - _httpContext = httpContext; - _elapsed = elapsed; + _context = context; + Elapsed = elapsed; } public override string ToString() { if (_cachedToString == null) { - _cachedToString = string.Format( - CultureInfo.InvariantCulture, - "Request finished in {0}ms {1} {2}", - _elapsed.TotalMilliseconds, - _httpContext.Response.StatusCode, - _httpContext.Response.ContentType); + var response = _context.HttpContext.Response; + _cachedToString = $"Request finished {_context.StartLog.ToStringWithoutPreamble()} - {response.StatusCode.ToString(CultureInfo.InvariantCulture)} {ValueOrEmptyMarker(response.ContentLength)} {EscapedValueOrEmptyMarker(response.ContentType)} {Elapsed.TotalMilliseconds.ToString("0.0000", CultureInfo.InvariantCulture)}ms"; } return _cachedToString; diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs index 279fa06aed..3a7586b1c9 100644 --- a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs +++ b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs @@ -5,12 +5,16 @@ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Net; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Hosting { internal class HostingRequestStartingLog : IReadOnlyList> { + private const string LogPreamble = "Request starting "; + private const string EmptyEntry = "-"; + internal static readonly Func Callback = (state, exception) => ((HostingRequestStartingLog)state).ToString(); private readonly HttpRequest _request; @@ -19,35 +23,19 @@ namespace Microsoft.AspNetCore.Hosting public int Count => 9; - public KeyValuePair this[int index] + public KeyValuePair this[int index] => index switch { - get - { - switch (index) - { - case 0: - return new KeyValuePair("Protocol", _request.Protocol); - case 1: - return new KeyValuePair("Method", _request.Method); - case 2: - return new KeyValuePair("ContentType", _request.ContentType); - case 3: - return new KeyValuePair("ContentLength", _request.ContentLength); - case 4: - return new KeyValuePair("Scheme", _request.Scheme); - case 5: - return new KeyValuePair("Host", _request.Host.ToString()); - case 6: - return new KeyValuePair("PathBase", _request.PathBase.ToString()); - case 7: - return new KeyValuePair("Path", _request.Path.ToString()); - case 8: - return new KeyValuePair("QueryString", _request.QueryString.ToString()); - default: - throw new IndexOutOfRangeException(nameof(index)); - } - } - } + 0 => new KeyValuePair(nameof(_request.Protocol), _request.Protocol), + 1 => new KeyValuePair(nameof(_request.Method), _request.Method), + 2 => new KeyValuePair(nameof(_request.ContentType), _request.ContentType), + 3 => new KeyValuePair(nameof(_request.ContentLength), _request.ContentLength), + 4 => new KeyValuePair(nameof(_request.Scheme), _request.Scheme), + 5 => new KeyValuePair(nameof(_request.Host), _request.Host.Value), + 6 => new KeyValuePair(nameof(_request.PathBase), _request.PathBase.Value), + 7 => new KeyValuePair(nameof(_request.Path), _request.Path.Value), + 8 => new KeyValuePair(nameof(_request.QueryString), _request.QueryString.Value), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; public HostingRequestStartingLog(HttpContext httpContext) { @@ -58,18 +46,8 @@ namespace Microsoft.AspNetCore.Hosting { if (_cachedToString == null) { - _cachedToString = string.Format( - CultureInfo.InvariantCulture, - "Request starting {0} {1} {2}://{3}{4}{5}{6} {7} {8}", - _request.Protocol, - _request.Method, - _request.Scheme, - _request.Host.Value, - _request.PathBase.Value, - _request.Path.Value, - _request.QueryString.Value, - _request.ContentType, - _request.ContentLength); + var request = _request; + _cachedToString = $"{LogPreamble}{request.Protocol} {request.Method} {request.Scheme}://{request.Host.Value}{request.PathBase.Value}{request.Path.Value}{request.QueryString.Value} {EscapedValueOrEmptyMarker(request.ContentType)} {ValueOrEmptyMarker(request.ContentLength)}"; ; } return _cachedToString; @@ -87,5 +65,15 @@ namespace Microsoft.AspNetCore.Hosting { return GetEnumerator(); } + + internal string ToStringWithoutPreamble() + => ToString().Substring(LogPreamble.Length); + + internal static string EscapedValueOrEmptyMarker(string potentialValue) + // Encode space as + + => potentialValue?.Length > 0 ? potentialValue.Replace(' ', '+') : EmptyEntry; + + internal static string ValueOrEmptyMarker(T? potentialValue) where T : struct, IFormattable + => potentialValue?.ToString(null, CultureInfo.InvariantCulture) ?? EmptyEntry; } } diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index 6e976959e7..ec0d6bdd8b 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -355,7 +355,7 @@ namespace Microsoft.AspNetCore.Hosting public void Dispose() { - DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + DisposeAsync().GetAwaiter().GetResult(); } public async ValueTask DisposeAsync() diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs index 5eea2b8ce3..e1608bba80 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.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. namespace Microsoft.AspNetCore.Server.IntegrationTesting diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs b/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs deleted file mode 100644 index 732a598ab8..0000000000 --- a/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Hosting.Server.Features; -using System.Linq; - -namespace Microsoft.AspNetCore.Hosting -{ - public static class IWebHostExtensions - { - public static string GetAddress(this IWebHost host) - { - return host.ServerFeatures.Get().Addresses.First(); - } - } -} diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs index 7129ff73d3..b8688dec04 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs @@ -56,34 +56,5 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common } } } - - private const int BasePort = 5001; - private const int MaxPort = 8000; - private static int NextPort = BasePort; - - // GetNextPort doesn't check for HttpSys urlacls. - public static int GetNextHttpSysPort(string scheme) - { - while (NextPort < MaxPort) - { - var port = NextPort++; - - using (var server = new HttpListener()) - { - server.Prefixes.Add($"{scheme}://localhost:{port}/"); - try - { - server.Start(); - server.Stop(); - return port; - } - catch (HttpListenerException) - { - } - } - } - NextPort = BasePort; - throw new Exception("Failed to locate a free port."); - } } } diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs index aaac5b88a7..4899fbea4f 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs @@ -1,7 +1,9 @@ -// 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; +using System.Diagnostics; + namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common { public static class TestUriHelper @@ -34,7 +36,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common } else if (serverType == ServerType.HttpSys) { - return new UriBuilder(scheme, "localhost", TestPortHelper.GetNextHttpSysPort(scheme)).Uri; + Debug.Assert(scheme == "http", "Https not supported"); + return new UriBuilder(scheme, "localhost", 0).Uri; } else { diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs b/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs index b828a5868e..92cea4fb37 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting public const string NetCoreApp22 = "netcoreapp2.2"; public const string NetCoreApp30 = "netcoreapp3.0"; public const string NetCoreApp31 = "netcoreapp3.1"; + public const string NetCoreApp50 = "netcoreapp5.0"; public static bool Matches(string tfm1, string tfm2) { diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs index d5c0dc02ca..262ff80ce8 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.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; diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs index 06923f7c46..e6e724c98c 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs @@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?.*)$"); private const string ApplicationStartedMessage = "Application started. Press Ctrl+C to shut down."; - private const int RetryCount = 5; public Process HostProcess { get; private set; } public SelfHostDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) @@ -56,33 +55,23 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting DotnetPublish(); } + var hintUrl = TestUriHelper.BuildTestUri( + DeploymentParameters.ServerType, + DeploymentParameters.Scheme, + DeploymentParameters.ApplicationBaseUriHint, + DeploymentParameters.StatusMessagesEnabled); + // Launch the host process. - for (var i = 0; i < RetryCount; i++) - { - var hintUrl = TestUriHelper.BuildTestUri( - DeploymentParameters.ServerType, - DeploymentParameters.Scheme, - DeploymentParameters.ApplicationBaseUriHint, - DeploymentParameters.StatusMessagesEnabled); - var (actualUrl, hostExitToken) = await StartSelfHostAsync(hintUrl); + var (actualUrl, hostExitToken) = await StartSelfHostAsync(hintUrl); - if (DeploymentParameters.ServerType == ServerType.HttpSys && hostExitToken.IsCancellationRequested) - { - // Retry HttpSys deployments due to port conflicts. - continue; - } + Logger.LogInformation("Application ready at URL: {appUrl}", actualUrl); - Logger.LogInformation("Application ready at URL: {appUrl}", actualUrl); - - return new DeploymentResult( - LoggerFactory, - DeploymentParameters, - applicationBaseUri: actualUrl.ToString(), - contentRoot: DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath, - hostShutdownToken: hostExitToken); - } - - throw new Exception($"Failed to start Self hosted application after {RetryCount} retries."); + return new DeploymentResult( + LoggerFactory, + DeploymentParameters, + applicationBaseUri: actualUrl.ToString(), + contentRoot: DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath, + hostShutdownToken: hostExitToken); } } @@ -176,6 +165,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting Logger.LogInformation("host process ID {pid} shut down", HostProcess.Id); // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want + started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {HostProcess.ExitCode}")); + TriggerHostShutdown(hostExitTokenSource); }; @@ -187,6 +178,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { Logger.LogError("Error occurred while starting the process. Exception: {exception}", ex.ToString()); } + if (HostProcess.HasExited) { Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, HostProcess.Id, HostProcess.ExitCode); diff --git a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj index 8edf4ff3f0..ba625f4332 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj +++ b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core helpers to deploy applications to IIS Express, IIS, WebListener and Kestrel for testing. @@ -19,7 +19,6 @@ - diff --git a/src/Hosting/build.cmd b/src/Hosting/build.cmd deleted file mode 100644 index 2406296662..0000000000 --- a/src/Hosting/build.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -SET RepoRoot=%~dp0..\.. -%RepoRoot%\build.cmd -projects %~dp0**\*.*proj %* diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs index deafac6c18..46007e6a99 100644 --- a/src/Hosting/test/FunctionalTests/ShutdownTests.cs +++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests RuntimeArchitecture.x64) { EnvironmentName = "Shutdown", - TargetFramework = Tfm.NetCoreApp31, + TargetFramework = Tfm.NetCoreApp50, ApplicationType = ApplicationType.Portable, PublishApplicationBeforeDeployment = true, StatusMessagesEnabled = false diff --git a/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs index 5d5044eacc..8747a75f26 100644 --- a/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs +++ b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests public WebHostBuilderTests(ITestOutputHelper output) : base(output) { } public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.Kestrel) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); [ConditionalTheory] [MemberData(nameof(TestVariants))] diff --git a/src/Http/Authentication.Core/src/AuthenticationService.cs b/src/Http/Authentication.Core/src/AuthenticationService.cs index 7efce69ce8..9cc0807539 100644 --- a/src/Http/Authentication.Core/src/AuthenticationService.cs +++ b/src/Http/Authentication.Core/src/AuthenticationService.cs @@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Authentication var schemes = await GetAllSignInSchemeNames(); // CookieAuth is the only implementation of sign-in. - var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?"; + var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?"; if (string.IsNullOrEmpty(schemes)) { @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.Authentication { // CookieAuth is the only implementation of sign-in. return new InvalidOperationException(mismatchError - + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?"); + + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and SignInAsync(\"Cookies\",...)?"); } return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}."); @@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Authentication { var schemes = await GetAllSignOutSchemeNames(); - var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?"; + var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?"; if (string.IsNullOrEmpty(schemes)) { @@ -314,7 +314,7 @@ namespace Microsoft.AspNetCore.Authentication { // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it. return new InvalidOperationException(mismatchError - + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?"); + + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?"); } return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}."); diff --git a/src/Http/Http/src/Features/RequestServicesFeature.cs b/src/Http/Http/src/Features/RequestServicesFeature.cs index 6585127367..5a1a714998 100644 --- a/src/Http/Http/src/Features/RequestServicesFeature.cs +++ b/src/Http/Http/src/Features/RequestServicesFeature.cs @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Http.Features public void Dispose() { - DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + DisposeAsync().GetAwaiter().GetResult(); } } } diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs index fd9c993bad..da24578135 100644 --- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs @@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.WebUtilities { if (Disposed) { - throw new ObjectDisposedException(nameof(FileBufferingReadStream)); + throw new ObjectDisposedException(nameof(FileBufferingWriteStream)); } } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml index e0053be7fd..f73e2fe556 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml @@ -17,7 +17,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml index b938724210..7541125984 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml index a58edae8a8..8ebfba2d1b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml @@ -12,7 +12,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml index 94242c929e..28b23ae8e1 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml @@ -11,7 +11,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml index abaad2a4d4..9f0e526bcf 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml @@ -13,7 +13,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml index 701c1ea457..0cdc9c97a7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml index e1a2b7a8a2..4939c7fd5a 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml @@ -16,7 +16,7 @@
-
+
@if (Model.RequirePassword) {
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml index 19ac1a296e..2adbdecf6f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml index 31632c3860..e82ce19df5 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml @@ -14,7 +14,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml index cf6a7c6e96..78415afe1a 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml index 5ccb61edcf..97629b27cd 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml index d92664c31c..7579138faa 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml @@ -17,7 +17,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml index d3eb7ce65d..4570844df8 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml index b98655e0f6..7db2738277 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml @@ -12,7 +12,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml index b5508902f6..5cc2aa2d13 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml @@ -11,7 +11,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml index 957f72b00f..5947903f87 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml @@ -13,7 +13,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml index 07e23b117c..4d5dda19ec 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml index e1896604ff..abcd16a82f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml @@ -15,7 +15,7 @@
-
+
@if (Model.RequirePassword) {
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml index dd25959817..1c125b18f7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml index 2a42c34f36..593526746e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml @@ -14,7 +14,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml index 99620a5836..0a2f84f935 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml index 3202efb8ec..953261844b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml index 6b2de21b43..23593ab99a 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml +++ b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml @@ -9,7 +9,7 @@
-
+
diff --git a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml index 259373e966..42297ce4d6 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml +++ b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml index 0bd3f75c1a..c9151a4239 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml @@ -9,7 +9,7 @@

Association Form


-
+

You've successfully authenticated with @ViewData["ProviderDisplayName"]. diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml index 5603e2a08c..74f769a750 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml @@ -11,7 +11,7 @@ @*

Enter your email.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml index b8803908e1..92e5a4a89a 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml @@ -15,7 +15,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml index 44e11d6955..f2d23a465c 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml @@ -8,7 +8,7 @@

Create a new account.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml index 343fcd8d33..c815424623 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml @@ -8,7 +8,7 @@

Reset your password.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml index 7c74d72a23..dd52938373 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]


diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml index f675dd1535..04ead74dbb 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]


diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml index 330a659ca7..4f15eda730 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]

diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml index 40885f3832..679dcb0a05 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml @@ -7,7 +7,7 @@

Add a phone number.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml index a8d076821b..4d3b38c13c 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml @@ -8,7 +8,7 @@

Change Password Form


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml index d1a1b77a44..ccd5781882 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml @@ -11,7 +11,7 @@

Set your password


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml index 076c20562c..6c4718a6f7 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml @@ -10,7 +10,7 @@

Add a phone number.

@ViewData["Status"]

-
+
diff --git a/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml b/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml index 91d36c62b0..0bcefd7555 100644 --- a/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml +++ b/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs b/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs index ed3f743dfc..0b8ecc393e 100644 --- a/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs +++ b/src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure return InvokeCoreAwaited(context, policyTask); } - corsPolicy = policyTask.GetAwaiter().GetResult(); + corsPolicy = policyTask.Result; } return EvaluateAndApplyPolicy(context, corsPolicy); diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs b/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs index 7fa998f296..6481bd29d3 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.cs +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/src/DbContextHealthCheck.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; @@ -49,8 +49,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { return HealthCheckResult.Healthy(); } - - return HealthCheckResult.Unhealthy(); + + return new HealthCheckResult(context.Registration.FailureStatus); } } } diff --git a/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs b/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs index fe5383c6f5..df22c3361f 100644 --- a/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.cs +++ b/src/Middleware/HealthChecks.EntityFrameworkCore/test/DbContextHealthCheckTest.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; @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } [Fact] - public async Task CheckAsync_CustomTest_Degraded() + public async Task CheckAsync_CustomTestWithDegradedFailureStatusSpecified_Degraded() { // Arrange var services = CreateServices(async (c, ct) => @@ -78,12 +78,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); // Assert - Assert.Equal(HealthStatus.Unhealthy, result.Status); + Assert.Equal(HealthStatus.Degraded, result.Status); } } [Fact] - public async Task CheckAsync_CustomTest_Unhealthy() + public async Task CheckAsync_CustomTestWithUnhealthyFailureStatusSpecified_Unhealthy() { // Arrange var services = CreateServices(async (c, ct) => @@ -104,12 +104,34 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } } + [Fact] + public async Task CheckAsync_CustomTestWithNoFailureStatusSpecified_Unhealthy() + { + // Arrange + var services = CreateServices(async (c, ct) => + { + return 0 < await c.Blogs.CountAsync(); + }, failureStatus: null); + + using (var scope = services.GetRequiredService().CreateScope()) + { + var registration = Assert.Single(services.GetRequiredService>().Value.Registrations); + var check = ActivatorUtilities.CreateInstance>(scope.ServiceProvider); + + // Act + var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); + + // Assert + Assert.Equal(HealthStatus.Unhealthy, result.Status); + } + } + // used to ensure each test uses a unique in-memory database private static int _testDbCounter; private static IServiceProvider CreateServices( Func> testQuery = null, - HealthStatus failureStatus = HealthStatus.Unhealthy) + HealthStatus? failureStatus = HealthStatus.Unhealthy) { var serviceCollection = new ServiceCollection(); serviceCollection.AddDbContext(o => o.UseInMemoryDatabase("Test" + Interlocked.Increment(ref _testDbCounter))); diff --git a/src/Middleware/Localization/Localization.slnf b/src/Middleware/Localization/Localization.slnf new file mode 100644 index 0000000000..e4494b3619 --- /dev/null +++ b/src/Middleware/Localization/Localization.slnf @@ -0,0 +1,16 @@ +{ + "solution": { + "path": "..\\Middleware.sln", + "projects": [ + "Localization.Routing\\src\\Microsoft.AspNetCore.Localization.Routing.csproj", + "Localization.Routing\\test\\Microsoft.AspNetCore.Localization.Routing.Tests.csproj", + "Localization\\sample\\LocalizationSample.csproj", + "Localization\\src\\Microsoft.AspNetCore.Localization.csproj", + "Localization\\test\\FunctionalTests\\Microsoft.AspNetCore.Localization.FunctionalTests.csproj", + "Localization\\test\\UnitTests\\Microsoft.AspNetCore.Localization.Tests.csproj", + "Localization\\testassets\\LocalizationWebsite\\LocalizationWebsite.csproj", + "Localization\\testassets\\ResourcesClassLibraryNoAttribute\\ResourcesClassLibraryNoAttribute.csproj", + "Localization\\testassets\\ResourcesClassLibraryWithAttribute\\ResourcesClassLibraryWithAttribute.csproj" + ] + } +} \ No newline at end of file diff --git a/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp.cs b/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp.cs index 599db4aca6..558921ba6b 100644 --- a/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp.cs +++ b/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Builder public partial class RequestLocalizationOptions { public RequestLocalizationOptions() { } + public bool ApplyCurrentCultureToResponseHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Localization.RequestCulture DefaultRequestCulture { get { throw null; } set { } } public bool FallBackToParentCultures { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool FallBackToParentUICultures { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } diff --git a/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs b/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs index c6ec3e1c06..07277ecdd7 100644 --- a/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs +++ b/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs @@ -1,5 +1,5 @@ -// 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. +// 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; @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Localization { @@ -146,6 +147,11 @@ namespace Microsoft.AspNetCore.Localization SetCurrentThreadCulture(requestCulture); + if (_options.ApplyCurrentCultureToResponseHeaders) + { + context.Response.Headers.Add(HeaderNames.ContentLanguage, requestCulture.UICulture.Name); + } + await _next(context); } diff --git a/src/Middleware/Localization/src/RequestLocalizationOptions.cs b/src/Middleware/Localization/src/RequestLocalizationOptions.cs index 16776364f0..95ca74fe32 100644 --- a/src/Middleware/Localization/src/RequestLocalizationOptions.cs +++ b/src/Middleware/Localization/src/RequestLocalizationOptions.cs @@ -84,6 +84,11 @@ namespace Microsoft.AspNetCore.Builder /// public bool FallBackToParentUICultures { get; set; } = true; + /// + /// Gets or sets a value that determines if is applied to the response Content-Language header. + /// + public bool ApplyCurrentCultureToResponseHeaders { get; set; } + /// /// The cultures supported by the application. The will only set /// the current request culture to an entry in this list. diff --git a/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs b/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs index 5f09115be2..15b00dfacf 100644 --- a/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs +++ b/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Net; @@ -14,6 +14,15 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests { public class LocalizationTest { + [Fact] + public Task Localization_ContentLanguageHeader() + { + return RunTest( + typeof(StartupContentLanguageHeader), + "ar-YE", + "True ar-YE"); + } + [Fact] public Task Localization_CustomCulture() { diff --git a/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs new file mode 100644 index 0000000000..849743184c --- /dev/null +++ b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs @@ -0,0 +1,49 @@ +// 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.Globalization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace LocalizationWebsite +{ + public class StartupContentLanguageHeader + { + public void ConfigureServices(IServiceCollection services) + { + services.AddLocalization(); + } + + public void Configure( + IApplicationBuilder app) + { + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = new List() + { + new CultureInfo("ar-YE") + }, + SupportedUICultures = new List() + { + new CultureInfo("ar-YE") + }, + ApplyCurrentCultureToResponseHeaders = true + }); + + app.Run(async (context) => + { + var hasContentLanguageHeader = context.Response.Headers.ContainsKey(HeaderNames.ContentLanguage); + var contentLanguage = context.Response.Headers[HeaderNames.ContentLanguage].ToString(); + + await context.Response.WriteAsync(hasContentLanguageHeader.ToString()); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(contentLanguage); + }); + } + } +} \ No newline at end of file diff --git a/src/Middleware/Localization/testassets/LocalizationWebsite/StartupResourcesInClassLibrary.cs b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupResourcesInClassLibrary.cs index 092d09a37b..6cab0c27c6 100644 --- a/src/Middleware/Localization/testassets/LocalizationWebsite/StartupResourcesInClassLibrary.cs +++ b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupResourcesInClassLibrary.cs @@ -39,7 +39,7 @@ namespace LocalizationWebsite }); var noAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryNoAttribute.Model)); - var withAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryWithAttribute.Model)); + var withAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(Alternate.Namespace.Model)); var noAttributeAssembly = typeof(ResourcesClassLibraryNoAttribute.Model).GetTypeInfo().Assembly; var noAttributeName = new AssemblyName(noAttributeAssembly.FullName).Name; @@ -47,10 +47,10 @@ namespace LocalizationWebsite nameof(ResourcesClassLibraryNoAttribute.Model), noAttributeName); - var withAttributeAssembly = typeof(ResourcesClassLibraryWithAttribute.Model).GetTypeInfo().Assembly; + var withAttributeAssembly = typeof(Alternate.Namespace.Model).GetTypeInfo().Assembly; var withAttributeName = new AssemblyName(withAttributeAssembly.FullName).Name; var withAttributeNameStringLocalizer = stringLocalizerFactory.Create( - nameof(ResourcesClassLibraryWithAttribute.Model), + nameof(Alternate.Namespace.Model), withAttributeName); app.Run(async (context) => diff --git a/src/Middleware/Localization/testassets/ResourcesClassLibraryWithAttribute/Model.cs b/src/Middleware/Localization/testassets/ResourcesClassLibraryWithAttribute/Model.cs index c6ca99afa8..b1ff90f523 100644 --- a/src/Middleware/Localization/testassets/ResourcesClassLibraryWithAttribute/Model.cs +++ b/src/Middleware/Localization/testassets/ResourcesClassLibraryWithAttribute/Model.cs @@ -1,7 +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. -namespace ResourcesClassLibraryWithAttribute +namespace Alternate.Namespace { public class Model { diff --git a/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp.cs b/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp.cs index 97f23b740d..6691b79a64 100644 --- a/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp.cs +++ b/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp.cs @@ -41,6 +41,7 @@ namespace Microsoft.AspNetCore.SpaServices public SpaOptions() { } public Microsoft.AspNetCore.Http.PathString DefaultPage { get { throw null; } set { } } public Microsoft.AspNetCore.Builder.StaticFileOptions DefaultPageStaticFileOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string PackageManagerCommand { get { throw null; } set { } } public string SourcePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.TimeSpan StartupTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs index fa3e9cc449..24f0ba430f 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli { private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine - private readonly string _npmScriptName; + private readonly string _scriptName; /// /// Constructs an instance of . @@ -35,12 +35,13 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli throw new ArgumentException("Cannot be null or empty.", nameof(npmScript)); } - _npmScriptName = npmScript; + _scriptName = npmScript; } /// public async Task Build(ISpaBuilder spaBuilder) { + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -50,32 +51,33 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli var logger = LoggerFinder.GetOrCreateLogger( spaBuilder.ApplicationBuilder, nameof(AngularCliBuilder)); - var npmScriptRunner = new NpmScriptRunner( + var scriptRunner = new NodeScriptRunner( sourcePath, - _npmScriptName, + _scriptName, "--watch", - null); - npmScriptRunner.AttachToLogger(logger); + null, + pkgManagerCommand); + scriptRunner.AttachToLogger(logger); - using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut)) - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { - await npmScriptRunner.StdOut.WaitForMatch( + await scriptRunner.StdOut.WaitForMatch( new Regex("Date", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{_npmScriptName}' exited without indicating success.\n" + + $"The {pkgManagerCommand} script '{_scriptName}' exited without indicating success.\n" + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } catch (OperationCanceledException ex) { throw new InvalidOperationException( - $"The NPM script '{_npmScriptName}' timed out without indicating success. " + + $"The {pkgManagerCommand} script '{_scriptName}' timed out without indicating success. " + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs index 9090f7738b..c4e109b8f7 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs @@ -23,23 +23,24 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli public static void Attach( ISpaBuilder spaBuilder, - string npmScriptName) + string scriptName) { + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } - if (string.IsNullOrEmpty(npmScriptName)) + if (string.IsNullOrEmpty(scriptName)) { - throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); + throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, logger); + var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerCommand, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -62,27 +63,27 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli } private static async Task StartAngularCliServerAsync( - string sourcePath, string npmScriptName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting @angular/cli on port {portNumber}..."); - var npmScriptRunner = new NpmScriptRunner( - sourcePath, npmScriptName, $"--port {portNumber}", null); - npmScriptRunner.AttachToLogger(logger); + var scriptRunner = new NodeScriptRunner( + sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerCommand); + scriptRunner.AttachToLogger(logger); Match openBrowserLine; - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { - openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch( + openBrowserLine = await scriptRunner.StdOut.WaitForMatch( new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{npmScriptName}' exited without indicating that the " + + $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " + $"Angular CLI was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs index 28e63c8e35..8f8176447b 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.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.Builder; diff --git a/src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs similarity index 77% rename from src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs rename to src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs index 378ec5f9fa..f08abeb19c 100644 --- a/src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs +++ b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.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.Extensions.Logging; @@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.NodeServices.Npm /// Executes the script entries defined in a package.json file, /// capturing any output written to stdio. /// - internal class NpmScriptRunner + internal class NodeScriptRunner { public EventedStreamReader StdOut { get; } public EventedStreamReader StdErr { get; } private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1)); - public NpmScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars) + public NodeScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars, string pkgManagerCommand) { if (string.IsNullOrEmpty(workingDirectory)) { @@ -35,18 +35,23 @@ namespace Microsoft.AspNetCore.NodeServices.Npm throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); } - var npmExe = "npm"; + if (string.IsNullOrEmpty(pkgManagerCommand)) + { + throw new ArgumentException("Cannot be null or empty.", nameof(pkgManagerCommand)); + } + + var exeToRun = pkgManagerCommand; var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // On Windows, the NPM executable is a .cmd file, so it can't be executed + // On Windows, the node executable is a .cmd file, so it can't be executed // directly (except with UseShellExecute=true, but that's no good, because // it prevents capturing stdio). So we need to invoke it via "cmd /c". - npmExe = "cmd"; - completeArguments = $"/c npm {completeArguments}"; + exeToRun = "cmd"; + completeArguments = $"/c {pkgManagerCommand} {completeArguments}"; } - var processStartInfo = new ProcessStartInfo(npmExe) + var processStartInfo = new ProcessStartInfo(exeToRun) { Arguments = completeArguments, UseShellExecute = false, @@ -64,19 +69,19 @@ namespace Microsoft.AspNetCore.NodeServices.Npm } } - var process = LaunchNodeProcess(processStartInfo); + var process = LaunchNodeProcess(processStartInfo, pkgManagerCommand); StdOut = new EventedStreamReader(process.StandardOutput); StdErr = new EventedStreamReader(process.StandardError); } public void AttachToLogger(ILogger logger) { - // When the NPM task emits complete lines, pass them through to the real logger + // When the node task emits complete lines, pass them through to the real logger StdOut.OnReceivedLine += line => { if (!string.IsNullOrWhiteSpace(line)) { - // NPM tasks commonly emit ANSI colors, but it wouldn't make sense to forward + // Node tasks commonly emit ANSI colors, but it wouldn't make sense to forward // those to loggers (because a logger isn't necessarily any kind of terminal) logger.LogInformation(StripAnsiColors(line)); } @@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm private static string StripAnsiColors(string line) => AnsiColorRegex.Replace(line, string.Empty); - private static Process LaunchNodeProcess(ProcessStartInfo startInfo) + private static Process LaunchNodeProcess(ProcessStartInfo startInfo, string commandName) { try { @@ -119,8 +124,8 @@ namespace Microsoft.AspNetCore.NodeServices.Npm } catch (Exception ex) { - var message = $"Failed to start 'npm'. To resolve this:.\n\n" - + "[1] Ensure that 'npm' is installed and can be found in one of the PATH directories.\n" + var message = $"Failed to start '{commandName}'. To resolve this:.\n\n" + + $"[1] Ensure that '{commandName}' is installed and can be found in one of the PATH directories.\n" + $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n" + " Make sure the executable is in one of those directories, or update your PATH.\n\n" + "[2] See the InnerException for further details of the cause."; diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs index 78a7b4f03f..6566fef706 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs @@ -22,23 +22,24 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer public static void Attach( ISpaBuilder spaBuilder, - string npmScriptName) + string scriptName) { + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } - if (string.IsNullOrEmpty(npmScriptName)) + if (string.IsNullOrEmpty(scriptName)) { - throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); + throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start create-react-app and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, logger); + var portTask = StartCreateReactAppServerAsync(sourcePath, scriptName, pkgManagerCommand, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -61,7 +62,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer } private static async Task StartCreateReactAppServerAsync( - string sourcePath, string npmScriptName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting create-react-app server on port {portNumber}..."); @@ -71,11 +72,11 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer { "PORT", portNumber.ToString() }, { "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port }; - var npmScriptRunner = new NpmScriptRunner( - sourcePath, npmScriptName, null, envVars); - npmScriptRunner.AttachToLogger(logger); + var scriptRunner = new NodeScriptRunner( + sourcePath, scriptName, null, envVars, pkgManagerCommand); + scriptRunner.AttachToLogger(logger); - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { @@ -83,13 +84,13 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer // it doesn't do so until it's finished compiling, and even then only if there were // no compiler warnings. So instead of waiting for that, consider it ready as soon // as it starts listening for requests. - await npmScriptRunner.StdOut.WaitForMatch( + await scriptRunner.StdOut.WaitForMatch( new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{npmScriptName}' exited without indicating that the " + + $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " + $"create-react-app server was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs index f58a6d1a9d..346e839046 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.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.Builder; diff --git a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs index b2823396dc..59ccc1eda4 100644 --- a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs +++ b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs @@ -1,11 +1,10 @@ -// 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; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.FileProviders; -using System; namespace Microsoft.AspNetCore.SpaServices { @@ -15,6 +14,7 @@ namespace Microsoft.AspNetCore.SpaServices public class SpaOptions { private PathString _defaultPage = "/index.html"; + private string _packageManagerCommand = "npm"; /// /// Constructs a new instance of . @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices internal SpaOptions(SpaOptions copyFromOptions) { _defaultPage = copyFromOptions.DefaultPage; + _packageManagerCommand = copyFromOptions.PackageManagerCommand; DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions; SourcePath = copyFromOptions.SourcePath; } @@ -69,6 +70,26 @@ namespace Microsoft.AspNetCore.SpaServices /// public string SourcePath { get; set; } + /// + /// Gets or sets the name of the package manager executible, (e.g npm, + /// yarn) to run the SPA. + /// + /// The default value is 'npm'. + /// + public string PackageManagerCommand + { + get => _packageManagerCommand; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException($"The value for {nameof(PackageManagerCommand)} cannot be null or empty."); + } + + _packageManagerCommand = value; + } + } + /// /// Gets or sets the maximum duration that a request will wait for the SPA /// to become ready to serve to the client. diff --git a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs index 95e018a590..aafd630853 100644 --- a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs +++ b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs @@ -83,23 +83,35 @@ namespace Microsoft.AspNetCore.NodeServices.Util var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length); if (chunkLength == 0) { + if (_linesBuffer.Length > 0) + { + OnCompleteLine(_linesBuffer.ToString()); + _linesBuffer.Clear(); + } + OnClosed(); break; } OnChunk(new ArraySegment(buf, 0, chunkLength)); - var lineBreakPos = Array.IndexOf(buf, '\n', 0, chunkLength); - if (lineBreakPos < 0) + int lineBreakPos = -1; + int startPos = 0; + + // get all the newlines + while ((lineBreakPos = Array.IndexOf(buf, '\n', startPos, chunkLength - startPos)) >= 0 && startPos < chunkLength) { - _linesBuffer.Append(buf, 0, chunkLength); - } - else - { - _linesBuffer.Append(buf, 0, lineBreakPos + 1); + var length = (lineBreakPos + 1) - startPos; + _linesBuffer.Append(buf, startPos, length); OnCompleteLine(_linesBuffer.ToString()); _linesBuffer.Clear(); - _linesBuffer.Append(buf, lineBreakPos + 1, chunkLength - (lineBreakPos + 1)); + startPos = lineBreakPos + 1; + } + + // get the rest + if (lineBreakPos < 0 && startPos < chunkLength) + { + _linesBuffer.Append(buf, startPos, chunkLength); } } } diff --git a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp.cs b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp.cs index 424801bb14..155d816cb8 100644 --- a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp.cs +++ b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp.cs @@ -120,12 +120,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure { public SharedOptions() { } public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RedirectToAppendTrailingSlash { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } } } public abstract partial class SharedOptionsBase { protected SharedOptionsBase(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions sharedOptions) { } public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { get { throw null; } set { } } + public bool RedirectToAppendTrailingSlash { get { throw null; } set { } } public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } } protected Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions SharedOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } diff --git a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs index 3aabe2fe65..b3b4755789 100644 --- a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs @@ -80,17 +80,13 @@ namespace Microsoft.AspNetCore.StaticFiles { // If the path matches a directory but does not end in a slash, redirect to add the slash. // This prevents relative links from breaking. - if (!Helpers.PathEndsInSlash(context.Request.Path)) + if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash) { - context.Response.StatusCode = StatusCodes.Status301MovedPermanently; - var request = context.Request; - var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); - context.Response.Headers[HeaderNames.Location] = redirect; + Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } - // Match found, re-write the url. A later middleware will actually serve the file. - context.Request.Path = new PathString(context.Request.Path.Value + defaultFile); + context.Request.Path = new PathString(Helpers.GetPathValueWithSlash(context.Request.Path) + defaultFile); break; } } diff --git a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs index 2d0a07b509..e689b309e4 100644 --- a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs @@ -87,12 +87,9 @@ namespace Microsoft.AspNetCore.StaticFiles { // If the path matches a directory but does not end in a slash, redirect to add the slash. // This prevents relative links from breaking. - if (!Helpers.PathEndsInSlash(context.Request.Path)) + if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash) { - context.Response.StatusCode = StatusCodes.Status301MovedPermanently; - var request = context.Request; - var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); - context.Response.Headers[HeaderNames.Location] = redirect; + Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } diff --git a/src/Middleware/StaticFiles/src/Helpers.cs b/src/Middleware/StaticFiles/src/Helpers.cs index a7a49e9070..d9b29c082f 100644 --- a/src/Middleware/StaticFiles/src/Helpers.cs +++ b/src/Middleware/StaticFiles/src/Helpers.cs @@ -2,9 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.StaticFiles { @@ -12,7 +15,8 @@ namespace Microsoft.AspNetCore.StaticFiles { internal static IFileProvider ResolveFileProvider(IWebHostEnvironment hostingEnv) { - if (hostingEnv.WebRootFileProvider == null) { + if (hostingEnv.WebRootFileProvider == null) + { throw new InvalidOperationException("Missing FileProvider."); } return hostingEnv.WebRootFileProvider; @@ -28,6 +32,23 @@ namespace Microsoft.AspNetCore.StaticFiles return path.Value.EndsWith("/", StringComparison.Ordinal); } + internal static string GetPathValueWithSlash(PathString path) + { + if (!PathEndsInSlash(path)) + { + return path.Value + "/"; + } + return path.Value; + } + + internal static void RedirectToPathWithSlash(HttpContext context) + { + context.Response.StatusCode = StatusCodes.Status301MovedPermanently; + var request = context.Request; + var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); + context.Response.Headers[HeaderNames.Location] = redirect; + } + internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath) { var path = context.Request.Path; diff --git a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs index 1c1cc80ad5..d6f08129a1 100644 --- a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs +++ b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs @@ -42,5 +42,10 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure /// The file system used to locate resources /// public IFileProvider FileProvider { get; set; } + + /// + /// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this. + /// + public bool RedirectToAppendTrailingSlash { get; set; } = true; } } diff --git a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs index 16900ec6fb..9e41b96cdc 100644 --- a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs +++ b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs @@ -48,5 +48,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure get { return SharedOptions.FileProvider; } set { SharedOptions.FileProvider = value; } } + + /// + /// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this. + /// + public bool RedirectToAppendTrailingSlash + { + get { return SharedOptions.RedirectToAppendTrailingSlash; } + set { SharedOptions.RedirectToAppendTrailingSlash = value; } + } } } diff --git a/src/Middleware/StaticFiles/test/FunctionalTests/FallbackStaticFileTest.cs b/src/Middleware/StaticFiles/test/FunctionalTests/FallbackStaticFileTest.cs index 2d12ee7a30..58b80d2952 100644 --- a/src/Middleware/StaticFiles/test/FunctionalTests/FallbackStaticFileTest.cs +++ b/src/Middleware/StaticFiles/test/FunctionalTests/FallbackStaticFileTest.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) { var environment = server.Services.GetRequiredService(); - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var response = await client.GetAsync("hello"); var responseText = await response.Content.ReadAsStringAsync(); @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) { var environment = server.Services.GetRequiredService(); - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var response = await client.GetAsync("hello"); var responseText = await response.Content.ReadAsStringAsync(); diff --git a/src/Middleware/StaticFiles/test/FunctionalTests/Helpers.cs b/src/Middleware/StaticFiles/test/FunctionalTests/Helpers.cs new file mode 100644 index 0000000000..12e634cda1 --- /dev/null +++ b/src/Middleware/StaticFiles/test/FunctionalTests/Helpers.cs @@ -0,0 +1,17 @@ +// 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.Linq; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; + +namespace Microsoft.AspNetCore.StaticFiles +{ + public static class Helpers + { + public static string GetAddress(IWebHost server) + { + return server.ServerFeatures.Get().Addresses.First(); + } + } +} diff --git a/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs b/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs index f90c79ca12..b4ca20cf0a 100644 --- a/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) { - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var response = await client.GetAsync("TestDocument.txt"); @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) { - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var response = await client.GetAsync("TestDocument.txt"); @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) { - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var last = File.GetLastWriteTimeUtc(Path.Combine(AppContext.BaseDirectory, "TestDocument.txt")); var response = await client.GetAsync("TestDocument.txt"); @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.StaticFiles { var hostingEnvironment = server.Services.GetService(); - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl)); var response = await client.GetAsync(requestUrl); @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.StaticFiles { var hostingEnvironment = server.Services.GetService(); - using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + using (var client = new HttpClient { BaseAddress = new Uri(Helpers.GetAddress(server)) }) { var fileInfo = hostingEnvironment.WebRootFileProvider.GetFileInfo(Path.GetFileName(requestUrl)); var request = new HttpRequestMessage(HttpMethod.Head, requestUrl); @@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.StaticFiles using (var server = builder.Start(TestUrlHelper.GetTestUrl(serverType))) { // We don't use HttpClient here because it's disconnect behavior varies across platforms. - var socket = SendSocketRequestAsync(server.GetAddress(), "/TestDocument1MB.txt"); + var socket = SendSocketRequestAsync(Helpers.GetAddress(server), "/TestDocument1MB.txt"); await requestReceived.Task.TimeoutAfter(interval); socket.LingerState = new LingerOption(true, 0); diff --git a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs index ab456fd218..08e86714a4 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs @@ -38,9 +38,14 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/subdir", @".", "/subdir/missing.dir")] [InlineData("/subdir", @".", "/subdir/missing.dir/")] [InlineData("", @"./", "/missing.dir")] - public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/missing.dir", false)] + [InlineData("", @".", "/missing.dir/", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir/", false)] + [InlineData("", @"./", "/missing.dir", false)] + public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -48,12 +53,14 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("", @".\", "/missing.dir")] [InlineData("", @".\", "/Missing.dir")] - public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/missing.dir", false)] + [InlineData("", @".\", "/Missing.dir", false)] + public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -62,7 +69,8 @@ namespace Microsoft.AspNetCore.StaticFiles app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }); app.Run(context => context.Response.WriteAsync(context.Request.Path.Value)); }); @@ -102,7 +110,7 @@ namespace Microsoft.AspNetCore.StaticFiles FileProvider = fileProvider }); - app.UseEndpoints(endpoints => {}); + app.UseEndpoints(endpoints => { }); }, services => { services.AddDirectoryBrowser(); services.AddRouting(); }); @@ -118,9 +126,19 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @"./SubFolder", "/")] [InlineData("", @"./SubFolder", "/你好/")] [InlineData("", @"./SubFolder", "/你好/世界/")] - public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("", @"./", "/SubFolder/", false)] + [InlineData("", @"./SubFolder", "/", false)] + [InlineData("", @"./SubFolder", "/你好/", false)] + [InlineData("", @"./SubFolder", "/你好/世界/", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("", @"./", "/SubFolder", false)] + [InlineData("", @"./SubFolder", "", false)] + [InlineData("", @"./SubFolder", "/你好", false)] + [InlineData("", @"./SubFolder", "/你好/世界", false)] + public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl); + await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -130,12 +148,20 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".\subFolder", "/")] [InlineData("", @".\SubFolder", "/你好/")] [InlineData("", @".\SubFolder", "/你好/世界/")] - public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/SubFolder/", false)] + [InlineData("", @".\subFolder", "/", false)] + [InlineData("", @".\SubFolder", "/你好/", false)] + [InlineData("", @".\SubFolder", "/你好/世界/", false)] + [InlineData("", @".\", "/SubFolder", false)] + [InlineData("", @".\subFolder", "", false)] + [InlineData("", @".\SubFolder", "/你好", false)] + [InlineData("", @".\SubFolder", "/你好/世界", false)] + public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl); + await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl) + private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -144,14 +170,17 @@ namespace Microsoft.AspNetCore.StaticFiles app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }); app.Run(context => context.Response.WriteAsync(context.Request.Path.Value)); }); var response = await server.CreateClient().GetAsync(requestUrl); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(requestUrl + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified + var requestUrlWithSlash = requestUrl.EndsWith("/") ? requestUrl : requestUrl + "/"; + Assert.Equal(requestUrlWithSlash + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified and be valid path to file } } @@ -202,9 +231,17 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/SubFolder", @".", "/somedir/")] [InlineData("", @"./SubFolder", "/")] [InlineData("", @"./SubFolder/", "/")] - public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("/SubFolder", @"./", "/SubFolder/", false)] + [InlineData("/SubFolder", @".", "/somedir/", false)] + [InlineData("", @"./SubFolder", "/", false)] + [InlineData("", @"./SubFolder/", "/", false)] + [InlineData("/SubFolder", @"./", "/SubFolder", false)] + [InlineData("/SubFolder", @".", "/somedir", false)] + [InlineData("", @"./SubFolder", "", false)] + [InlineData("", @"./SubFolder/", "", false)] + public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -213,24 +250,37 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/SubFolder", @".\", "/SubFolder/")] [InlineData("", @".\SubFolder", "/")] [InlineData("", @".\SubFolder\", "/")] - public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/SubFolder", @".\", "/SubFolder/", false)] + [InlineData("", @".\SubFolder", "/", false)] + [InlineData("", @".\SubFolder\", "/", false)] + [InlineData("/SubFolder", @".\", "/SubFolder", false)] + [InlineData("", @".\SubFolder", "", false)] + [InlineData("", @".\SubFolder\", "", false)] + public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash })); var response = await server.CreateRequest(requestUrl).GetAsync(); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); // Passed through } } + + [Fact] + public void Options_AppendTrailingSlashByDefault() + { + Assert.True(new DefaultFilesOptions().RedirectToAppendTrailingSlash); + } } } diff --git a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs index 3e909a24ab..5a39ec3ef9 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs @@ -56,9 +56,14 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/subdir", @".", "/subdir/missing.dir")] [InlineData("/subdir", @".", "/subdir/missing.dir/")] [InlineData("", @"./", "/missing.dir")] - public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/missing.dir", false)] + [InlineData("", @".", "/missing.dir/", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir/", false)] + [InlineData("", @"./", "/missing.dir", false)] + public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -66,12 +71,14 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("", @".\", "/missing.dir")] [InlineData("", @".\", "/Missing.dir")] - public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/missing.dir", false)] + [InlineData("", @".\", "/Missing.dir", false)] + public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -79,7 +86,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); var response = await server.CreateRequest(requestUrl).GetAsync(); @@ -117,7 +125,7 @@ namespace Microsoft.AspNetCore.StaticFiles FileProvider = fileProvider }); - app.UseEndpoints(endpoints => {}); + app.UseEndpoints(endpoints => { }); }, services => { services.AddDirectoryBrowser(); services.AddRouting(); }); @@ -133,9 +141,19 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @"./", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @"./", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @"./", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectory_Served(baseUrl, baseDir, requestUrl); + await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -143,12 +161,16 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".\", "/somedir/")] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".\", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".\", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectory_Served(baseUrl, baseDir, requestUrl); + await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl) + private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -156,7 +178,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash, }), services => services.AddDirectoryBrowser()); var response = await server.CreateRequest(requestUrl).GetAsync(); @@ -215,21 +238,31 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".", "/SubFolder/")] [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -237,7 +270,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); @@ -251,21 +285,31 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".", "/SubFolder/")] [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl); + await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl); + await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl) + private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -273,7 +317,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); @@ -285,5 +330,11 @@ namespace Microsoft.AspNetCore.StaticFiles Assert.Empty((await response.Content.ReadAsByteArrayAsync())); } } + + [Fact] + public void Options_AppendTrailingSlashByDefault() + { + Assert.True(new DirectoryBrowserOptions().RedirectToAppendTrailingSlash); + } } } diff --git a/src/Middleware/WebSockets/test/ConformanceTests/Autobahn/AutobahnTester.cs b/src/Middleware/WebSockets/test/ConformanceTests/Autobahn/AutobahnTester.cs index f4b4a2fd8e..7e985a0599 100644 --- a/src/Middleware/WebSockets/test/ConformanceTests/Autobahn/AutobahnTester.cs +++ b/src/Middleware/WebSockets/test/ConformanceTests/Autobahn/AutobahnTester.cs @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn { Scheme = (ssl ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), ApplicationType = ApplicationType.Portable, - TargetFramework = "netcoreapp3.1", + TargetFramework = "netcoreapp5.0", EnvironmentName = environment, SiteName = "HttpTestSite", // This is configured in the Http.config ServerConfigTemplateContent = (server == ServerType.IISExpress) ? File.ReadAllText(configPath) : null, diff --git a/src/MusicStore/test/MusicStore.E2ETests/DotnetRunTests.cs b/src/MusicStore/test/MusicStore.E2ETests/DotnetRunTests.cs index a77a7ee26d..ed7fb66de9 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/DotnetRunTests.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/DotnetRunTests.cs @@ -17,7 +17,7 @@ namespace E2ETests { public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.Kestrel) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); [ConditionalTheory] [MemberData(nameof(TestVariants))] diff --git a/src/MusicStore/test/MusicStore.E2ETests/NtlmAuthentationTest.cs b/src/MusicStore/test/MusicStore.E2ETests/NtlmAuthentationTest.cs index 27f1a89955..ee2973fe1b 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/NtlmAuthentationTest.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/NtlmAuthentationTest.cs @@ -18,7 +18,7 @@ namespace E2ETests { public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllArchitectures(); diff --git a/src/MusicStore/test/MusicStore.E2ETests/OpenIdConnectTests.cs b/src/MusicStore/test/MusicStore.E2ETests/OpenIdConnectTests.cs index 72a7433d51..5e59c33c38 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/OpenIdConnectTests.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/OpenIdConnectTests.cs @@ -15,7 +15,7 @@ namespace E2ETests { public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); [ConditionalTheory] [MemberData(nameof(TestVariants))] diff --git a/src/MusicStore/test/MusicStore.E2ETests/PublishAndRunTests.cs b/src/MusicStore/test/MusicStore.E2ETests/PublishAndRunTests.cs index baee1468f5..d0760c261f 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/PublishAndRunTests.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/PublishAndRunTests.cs @@ -16,7 +16,7 @@ namespace E2ETests { public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels() .WithAllArchitectures(); diff --git a/src/MusicStore/test/MusicStore.E2ETests/SmokeTests.cs b/src/MusicStore/test/MusicStore.E2ETests/SmokeTests.cs index 5fd0c206d3..26d8cd2771 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/SmokeTests.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/SmokeTests.cs @@ -17,7 +17,7 @@ namespace E2ETests { public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/MusicStore/test/MusicStore.E2ETests/SmokeTestsOnNanoServer.cs b/src/MusicStore/test/MusicStore.E2ETests/SmokeTestsOnNanoServer.cs index 9d6b9dea44..490d2148ce 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/SmokeTestsOnNanoServer.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/SmokeTestsOnNanoServer.cs @@ -244,7 +244,7 @@ namespace E2ETests _remoteDeploymentConfig.AccountName, _remoteDeploymentConfig.AccountPassword) { - TargetFramework = Tfm.NetCoreApp31, + TargetFramework = Tfm.NetCoreApp50, ApplicationBaseUriHint = applicationBaseUrl, ApplicationType = applicationType }; diff --git a/src/MusicStore/test/MusicStore.E2ETests/StoreSmokeTests.cs b/src/MusicStore/test/MusicStore.E2ETests/StoreSmokeTests.cs index f5f57f4c04..5995c16391 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/StoreSmokeTests.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/StoreSmokeTests.cs @@ -34,7 +34,7 @@ namespace E2ETests EnvironmentName = "SocialTesting", PublishApplicationBeforeDeployment = true, PreservePublishedApplicationForDebugging = Helpers.PreservePublishedApplicationForDebugging, - TargetFramework = Tfm.NetCoreApp31, + TargetFramework = Tfm.NetCoreApp50, UserAdditionalCleanup = parameters => { DbUtils.DropDatabase(musicStoreDbName, logger); diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs index 4e57509298..55d1f63af9 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IValueProvider.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Retrieves a value object using the specified key. /// /// The key of the value object to retrieve. - /// The value object for the specified key. If the exact key is not found, null. + /// The value object for the specified key. If the exact key is not found, . ValueProviderResult GetValue(string key); } } diff --git a/src/Mvc/Mvc.Core/src/ControllerBase.cs b/src/Mvc/Mvc.Core/src/ControllerBase.cs index e502ca144a..3590d6146f 100644 --- a/src/Mvc/Mvc.Core/src/ControllerBase.cs +++ b/src/Mvc/Mvc.Core/src/ControllerBase.cs @@ -1848,7 +1848,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Creates an that produces a response. /// - /// The value for .. + /// The value for . /// The value for . /// The value for . /// The value for . diff --git a/src/Mvc/Mvc.Core/src/FileContentResult.cs b/src/Mvc/Mvc.Core/src/FileContentResult.cs index 0c33859001..b289f20413 100644 --- a/src/Mvc/Mvc.Core/src/FileContentResult.cs +++ b/src/Mvc/Mvc.Core/src/FileContentResult.cs @@ -27,10 +27,6 @@ namespace Microsoft.AspNetCore.Mvc public FileContentResult(byte[] fileContents, string contentType) : this(fileContents, MediaTypeHeaderValue.Parse(contentType)) { - if (fileContents == null) - { - throw new ArgumentNullException(nameof(fileContents)); - } } /// @@ -80,4 +76,4 @@ namespace Microsoft.AspNetCore.Mvc return executor.ExecuteAsync(context, this); } } -} \ No newline at end of file +} diff --git a/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs b/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs index dfe16c2421..5fffa77974 100644 --- a/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs +++ b/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { if (context == null) { - throw new ArgumentNullException(Resources.ArgumentCannotBeNullOrEmpty, (nameof(context))); + throw new ArgumentNullException(nameof(context)); } var httpContext = context.HttpContext; @@ -69,4 +69,4 @@ namespace Microsoft.AspNetCore.Mvc.Routing return urlHelper; } } -} \ No newline at end of file +} diff --git a/src/Mvc/Mvc.Core/src/VirtualFileResult.cs b/src/Mvc/Mvc.Core/src/VirtualFileResult.cs index af075b5a09..e44b0623ff 100644 --- a/src/Mvc/Mvc.Core/src/VirtualFileResult.cs +++ b/src/Mvc/Mvc.Core/src/VirtualFileResult.cs @@ -27,10 +27,6 @@ namespace Microsoft.AspNetCore.Mvc public VirtualFileResult(string fileName, string contentType) : this(fileName, MediaTypeHeaderValue.Parse(contentType)) { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } } /// diff --git a/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs b/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs index e5c390395b..15d2341e12 100644 --- a/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs +++ b/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs @@ -317,11 +317,11 @@ namespace Microsoft.AspNetCore.Mvc.Authorization public async Task AuthorizationFilterCombinesMultipleFilters() { // Arrange - var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); var authorizationContext = GetAuthorizationContext(anonymous: false); // Effective policy should fail, if both are combined authorizationContext.Filters.Add(authorizeFilter); - var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); + var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); authorizationContext.Filters.Add(secondFilter); // Act diff --git a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs index 411725c7a9..d6af5e8f72 100644 --- a/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs +++ b/src/Mvc/Mvc.Core/test/Formatters/SystemTextJsonInputFormatterTest.cs @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters internal override string ReadAsync_AddsModelValidationErrorsToModelState_Expected => "$.Age"; - internal override string JsonFormatter_EscapedKeys_Expected => "$[0]['It\\u0022s a key']"; + internal override string JsonFormatter_EscapedKeys_Expected => "$[0]['It\"s a key']"; internal override string JsonFormatter_EscapedKeys_Bracket_Expected => "$[0]['It[s a key']"; diff --git a/src/Mvc/Mvc.DataAnnotations/src/ValidationAttributeAdapterProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/ValidationAttributeAdapterProvider.cs index 7ec543d39d..a32a28a032 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/ValidationAttributeAdapterProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/ValidationAttributeAdapterProvider.cs @@ -29,51 +29,51 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations var type = attribute.GetType(); - if (type == typeof(RegularExpressionAttribute)) + if (typeof(RegularExpressionAttribute).IsAssignableFrom(type)) { adapter = new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute, stringLocalizer); } - else if (type == typeof(MaxLengthAttribute)) + else if (typeof(MaxLengthAttribute).IsAssignableFrom(type)) { adapter = new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer); } - else if (type == typeof(RequiredAttribute)) + else if (typeof(RequiredAttribute).IsAssignableFrom(type)) { adapter = new RequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer); } - else if (type == typeof(CompareAttribute)) + else if (typeof(CompareAttribute).IsAssignableFrom(type)) { adapter = new CompareAttributeAdapter((CompareAttribute)attribute, stringLocalizer); } - else if (type == typeof(MinLengthAttribute)) + else if (typeof(MinLengthAttribute).IsAssignableFrom(type)) { adapter = new MinLengthAttributeAdapter((MinLengthAttribute)attribute, stringLocalizer); } - else if (type == typeof(CreditCardAttribute)) + else if (typeof(CreditCardAttribute).IsAssignableFrom(type)) { adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-creditcard", stringLocalizer); } - else if (type == typeof(StringLengthAttribute)) + else if (typeof(StringLengthAttribute).IsAssignableFrom(type)) { adapter = new StringLengthAttributeAdapter((StringLengthAttribute)attribute, stringLocalizer); } - else if (type == typeof(RangeAttribute)) + else if (typeof(RangeAttribute).IsAssignableFrom(type)) { adapter = new RangeAttributeAdapter((RangeAttribute)attribute, stringLocalizer); } - else if (type == typeof(EmailAddressAttribute)) + else if (typeof(EmailAddressAttribute).IsAssignableFrom(type)) { adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-email", stringLocalizer); } - else if (type == typeof(PhoneAttribute)) + else if (typeof(PhoneAttribute).IsAssignableFrom(type)) { adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-phone", stringLocalizer); } - else if (type == typeof(UrlAttribute)) + else if (typeof(UrlAttribute).IsAssignableFrom(type)) { adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-url", stringLocalizer); } - else if (type == typeof(FileExtensionsAttribute)) + else if (typeof(FileExtensionsAttribute).IsAssignableFrom(type)) { adapter = new FileExtensionsAttributeAdapter((FileExtensionsAttribute)attribute, stringLocalizer); } diff --git a/src/Mvc/Mvc.DataAnnotations/test/ValidationAttributeAdapterProviderTest.cs b/src/Mvc/Mvc.DataAnnotations/test/ValidationAttributeAdapterProviderTest.cs index ef6027c5a1..51d0c6f4a7 100644 --- a/src/Mvc/Mvc.DataAnnotations/test/ValidationAttributeAdapterProviderTest.cs +++ b/src/Mvc/Mvc.DataAnnotations/test/ValidationAttributeAdapterProviderTest.cs @@ -41,6 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations { new RequiredAttribute(), typeof(RequiredAttributeAdapter) + }, + { + new CustomRegularExpressionAttribute("abc"), + typeof(RegularExpressionAttributeAdapter) } }; } @@ -85,5 +89,13 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations var dataTypeAdapter = Assert.IsType(adapter); Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName); } + + class CustomRegularExpressionAttribute : RegularExpressionAttribute + { + public CustomRegularExpressionAttribute(string pattern) : base(pattern) + { + ErrorMessage = "Not valid."; + } + } } } diff --git a/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp.cs b/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp.cs index fa9ffb071d..eae4a1a733 100644 --- a/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp.cs +++ b/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp.cs @@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters protected override bool CanReadType(System.Type type) { throw null; } protected virtual System.Xml.Serialization.XmlSerializer CreateSerializer(System.Type type) { throw null; } protected virtual System.Xml.XmlReader CreateXmlReader(System.IO.Stream readStream, System.Text.Encoding encoding) { throw null; } + protected virtual System.Xml.XmlReader CreateXmlReader(System.IO.Stream readStream, System.Text.Encoding encoding, System.Type type) { throw null; } protected virtual System.Xml.Serialization.XmlSerializer GetCachedSerializer(System.Type type) { throw null; } protected virtual System.Type GetSerializableType(System.Type declaredType) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs index 4debb6fe69..da40141ded 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs @@ -119,8 +119,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters try { - using var xmlReader = CreateXmlReader(readStream, encoding); - var type = GetSerializableType(context.ModelType); + var type = GetSerializableType(context.ModelType); + using var xmlReader = CreateXmlReader(readStream, encoding, type); var serializer = GetCachedSerializer(type); @@ -191,6 +191,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return wrapperProvider?.WrappingType ?? declaredType; } + /// + /// Called during deserialization to get the . + /// + /// The from which to read. + /// The used to read the stream. + /// The that is to be deserialized. + /// The used during deserialization. + protected virtual XmlReader CreateXmlReader(Stream readStream, Encoding encoding, Type type) + { + return CreateXmlReader(readStream, encoding); + } + /// /// Called during deserialization to get the . /// diff --git a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/build/netcoreapp3.0/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets b/src/Mvc/Mvc.Razor.RuntimeCompilation/src/build/netcoreapp5.0/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets similarity index 100% rename from src/Mvc/Mvc.Razor.RuntimeCompilation/src/build/netcoreapp3.0/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets rename to src/Mvc/Mvc.Razor.RuntimeCompilation/src/build/netcoreapp5.0/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets diff --git a/src/Mvc/Mvc.RazorPages/ref/Directory.Build.props b/src/Mvc/Mvc.RazorPages/ref/Directory.Build.props index bae646f06c..6cab2bf950 100644 --- a/src/Mvc/Mvc.RazorPages/ref/Directory.Build.props +++ b/src/Mvc/Mvc.RazorPages/ref/Directory.Build.props @@ -3,7 +3,7 @@ - + diff --git a/src/Mvc/Mvc.RazorPages/ref/Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp3.1.Manual.cs b/src/Mvc/Mvc.RazorPages/ref/Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp5.0.Manual.cs similarity index 100% rename from src/Mvc/Mvc.RazorPages/ref/Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp3.1.Manual.cs rename to src/Mvc/Mvc.RazorPages/ref/Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp5.0.Manual.cs diff --git a/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs index d3df4d963d..3ce7506e08 100644 --- a/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs @@ -309,29 +309,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers "checkbox")); } - // hiddenForCheckboxTag always rendered after the returned element - var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); - if (hiddenForCheckboxTag != null) + if (ViewContext.CheckBoxHiddenInputRenderMode != CheckBoxHiddenInputRenderMode.None) { - var renderingMode = - output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; - hiddenForCheckboxTag.TagRenderMode = renderingMode; - if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") && - !string.IsNullOrEmpty(Name)) + // hiddenForCheckboxTag always rendered after the returned element + var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); + if (hiddenForCheckboxTag != null) { - // The checkbox and hidden elements should have the same name attribute value. Attributes will - // match if both are present because both have a generated value. Reach here in the special case - // where user provided a non-empty fallback name. - hiddenForCheckboxTag.MergeAttribute("name", Name); - } + var renderingMode = + output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; + hiddenForCheckboxTag.TagRenderMode = renderingMode; + if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") && + !string.IsNullOrEmpty(Name)) + { + // The checkbox and hidden elements should have the same name attribute value. Attributes will + // match if both are present because both have a generated value. Reach here in the special case + // where user provided a non-empty fallback name. + hiddenForCheckboxTag.MergeAttribute("name", Name); + } - if (ViewContext.FormContext.CanRenderAtEndOfForm) - { - ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); - } - else - { - output.PostElement.AppendHtml(hiddenForCheckboxTag); + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.EndOfForm && ViewContext.FormContext.CanRenderAtEndOfForm) + { + ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); + } + else + { + output.PostElement.AppendHtml(hiddenForCheckboxTag); + } } } diff --git a/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs index 265ecde1f0..557454892b 100644 --- a/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs +++ b/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs @@ -838,6 +838,243 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeNone() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement))); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeInline() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedPostElementContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = true; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.Inline; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement)); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedEndOfFormContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = true; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.Equal(expectedEndOfFormContent, string.Join("", viewContext.FormContext.EndOfFormContent.Select(html => HtmlContentUtilities.HtmlContentToString(html)))); + Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement))); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm_AndCanRenderAtEndOfFormNotSet() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedPostElementContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = false; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement)); + } + [Fact] public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters() { diff --git a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs index e9618a4842..d607693165 100644 --- a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs +++ b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs @@ -312,6 +312,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } namespace Microsoft.AspNetCore.Mvc.Rendering { + public enum CheckBoxHiddenInputRenderMode + { + None = 0, + Inline = 1, + EndOfForm = 2, + } public enum FormMethod { Get = 0, @@ -684,6 +690,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering public ViewContext() { } public ViewContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewEngines.IView view, Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary viewData, Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary tempData, System.IO.TextWriter writer, Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelperOptions htmlHelperOptions) { } public ViewContext(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext, Microsoft.AspNetCore.Mvc.ViewEngines.IView view, Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary viewData, System.IO.TextWriter writer) { } + public Microsoft.AspNetCore.Mvc.Rendering.CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool ClientValidationEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ExecutingFilePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual Microsoft.AspNetCore.Mvc.ViewFeatures.FormContext FormContext { get { throw null; } set { } } @@ -1068,6 +1075,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public partial class HtmlHelperOptions { public HtmlHelperOptions() { } + public Microsoft.AspNetCore.Mvc.Rendering.CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool ClientValidationEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Mvc.Rendering.Html5DateRenderingMode Html5DateRenderingMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string IdAttributeDotReplacement { get { throw null; } set { } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs index 016246f4a4..cf8ee07fd9 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs @@ -721,8 +721,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures isChecked, htmlAttributes); + if (checkbox == null) + { + return HtmlString.Empty; + } + + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.None) + { + return checkbox; + } + var hiddenForCheckbox = _htmlGenerator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, expression); - if (checkbox == null || hiddenForCheckbox == null) + if (hiddenForCheckbox == null) { return HtmlString.Empty; } @@ -736,7 +746,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures hiddenForCheckbox.MergeAttribute("name", name); } - if (ViewContext.FormContext.CanRenderAtEndOfForm) + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.EndOfForm && ViewContext.FormContext.CanRenderAtEndOfForm) { ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckbox); return checkbox; diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs index 952bc7b4d9..f40bdf93f6 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs @@ -56,5 +56,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// and other overloads. /// public string ValidationSummaryMessageElement { get; set; } = "span"; + + /// + /// Gets or sets the way hidden inputs are rendered for checkbox tag helpers and html helpers. + /// + public CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { get; set; } = CheckBoxHiddenInputRenderMode.EndOfForm; } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs new file mode 100644 index 0000000000..3d89c44d80 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs @@ -0,0 +1,27 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + /// + /// Controls the rendering of hidden input fields when using CheckBox tag helpers or html helpers. + /// + public enum CheckBoxHiddenInputRenderMode + { + /// + /// Hidden input fields will not be automatically rendered. If checkbox is not checked, no value will be posted. + /// + None = 0, + + /// + /// Hidden input fields will be rendered inline with each checkbox. Use this for legacy ASP.NET MVC behavior. + /// + Inline = 1, + + /// + /// Hidden input fields will be rendered for each checkbox at the bottom of the form element. This is the preferred render method and default MVC behavior. + /// If is false, will fall back on . + /// + EndOfForm = 2 + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs index 0604cfc26e..ac2d6c48c9 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs @@ -89,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode; ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement; ValidationMessageElement = htmlHelperOptions.ValidationMessageElement; + CheckBoxHiddenInputRenderMode = htmlHelperOptions.CheckBoxHiddenInputRenderMode; } /// @@ -131,6 +132,8 @@ namespace Microsoft.AspNetCore.Mvc.Rendering Html5DateRenderingMode = viewContext.Html5DateRenderingMode; ValidationSummaryMessageElement = viewContext.ValidationSummaryMessageElement; ValidationMessageElement = viewContext.ValidationMessageElement; + CheckBoxHiddenInputRenderMode = viewContext.CheckBoxHiddenInputRenderMode; + ExecutingFilePath = viewContext.ExecutingFilePath; View = view; ViewData = viewData; @@ -184,6 +187,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering /// public string ValidationMessageElement { get; set; } + /// + /// Gets or sets the way hidden inputs are rendered for checkbox tag helpers and html helpers. + /// + public CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { get; set; } + /// /// Gets the dynamic view bag. /// diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs index 67cfb7f64e..ab35762255 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs @@ -169,6 +169,93 @@ namespace Microsoft.AspNetCore.Mvc.Rendering writer.ToString()); } + [Fact] + public void CheckBox_WithHiddenInputRenderModeNone_DoesNotGenerateHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.False(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeInline_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = true; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.Inline; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeEndOfForm_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = true; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.True(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + var writer = new StringWriter(); + var hiddenTag = Assert.Single(helper.ViewContext.FormContext.EndOfFormContent); + hiddenTag.WriteTo(writer, new HtmlTestEncoder()); + Assert.Equal("", + writer.ToString()); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeEndOfForm_WithCanRenderAtEndOfFormNotSet_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = false; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.False(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + [Fact] public void CheckBoxUsesAttemptedValueFromModelState() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json index b6d3006d45..9c2c9d2ed6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json @@ -337,12 +337,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor index f2f9098007..842ef1f6bc 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor @@ -7,7 +7,7 @@
- About + About
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor index 9b5407d03b..74820a0b75 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor @@ -6,7 +6,7 @@
- About + About
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json index 0ee8c1fd71..32039a6734 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json @@ -86,12 +86,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json index 13fc5299e5..0ebccbad52 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json @@ -82,12 +82,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json index 9499795d70..462630dbbd 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json @@ -41,11 +41,11 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "defaultValue": "netcoreapp3.1" + "defaultValue": "netcoreapp5.0" }, "ExcludeLaunchSettings": { "type": "parameter", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json index e03bc65429..d99bc528b9 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json @@ -4,8 +4,7 @@ "classifications": [ "Web", "Razor", - "Library", - "Razor Class Library" + "Library" ], "name": "Razor Class Library", "generatorVersions": "[1.0.0.0-*)", @@ -48,11 +47,11 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "defaultValue": "netcoreapp3.1" + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json index d027fb97e4..82977b366b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json @@ -310,12 +310,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json index fd4e26bef3..06954164c1 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json @@ -300,12 +300,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json index 5e95e2b1af..d72e90aaff 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json @@ -87,12 +87,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index f48680bf91..a6e465575e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -209,12 +209,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json index 75dcdd58f3..20f31e8bcd 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json @@ -82,12 +82,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json index 408eae476b..f0428fa74f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json @@ -47,12 +47,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "copyrightYear": { "type": "generated", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json index 8855782667..6bf8ed94b4 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json @@ -177,12 +177,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/angular.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/angular.json index 56f93675c6..ce5929d3f1 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/angular.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/angular.json @@ -71,7 +71,7 @@ "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "src/karma.conf.js", - "styles": ["styles.css"], + "styles": ["src/styles.css"], "scripts": [], "assets": ["src/assets"] } diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs index 75e26da4e9..cdcc89182a 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs @@ -6,12 +6,12 @@ namespace Company.WebApplication1.Controllers { public class OidcConfigurationController : Controller { - private readonly ILogger logger; + private readonly ILogger _logger; - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger _logger) + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger logger) { ClientRequestParametersProvider = clientRequestParametersProvider; - logger = _logger; + _logger = logger; } public IClientRequestParametersProvider ClientRequestParametersProvider { get; } diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json index a1079ad570..d1add605c5 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json @@ -178,12 +178,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json index 301aa0d2df..0f85d08a4d 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json @@ -87,12 +87,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env new file mode 100644 index 0000000000..6ce384e5ce --- /dev/null +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.env @@ -0,0 +1 @@ +BROWSER=none diff --git a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 index ba4180de95..ec8993da7e 100644 --- a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 index 7d989b65a3..575036e9c7 100644 --- a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 index 6aeffd7687..d6859c6f72 100644 --- a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 index f77838b435..ecba4fbd8f 100644 --- a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 @@ -6,4 +6,4 @@ param() . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 index 972bfc4ead..8308860edc 100644 --- a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 index 4c01f11400..6100d7cacd 100644 --- a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1.3.1.0-dev.nupkg" $true +Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 index 7971ae01ac..61ad47fbdc 100644 --- a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 index 94fd8d012b..e6ff856e7e 100644 --- a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.3.1.3.1.0-dev.nupkg" $false +Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Test-Template.ps1 b/src/ProjectTemplates/scripts/Test-Template.ps1 index d13b7e452c..5ad25a16d6 100644 --- a/src/ProjectTemplates/scripts/Test-Template.ps1 +++ b/src/ProjectTemplates/scripts/Test-Template.ps1 @@ -32,7 +32,7 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isSPA) { $proj = "$tmpDir/$templateName.$extension" $projContent = Get-Content -Path $proj -Raw $projContent = $projContent -replace ('', " - + @@ -42,7 +42,7 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isSPA) { $projContent | Set-Content $proj dotnet.exe ef migrations add mvc dotnet.exe publish --configuration Release - dotnet.exe bin\Release\netcoreapp3.1\publish\$templateName.dll + dotnet.exe bin\Release\netcoreapp5.0\publish\$templateName.dll } finally { Pop-Location diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index bf9fe12f2c..a0b8f6cc60 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -36,7 +36,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); @@ -93,7 +93,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index 64f5d8cf3e..14fae1e2d1 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -32,11 +32,17 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("web", language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); + // Avoid the F# compiler. See https://github.com/aspnet/AspNetCore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs index e768bc3b23..5ac3cde3ec 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -21,7 +21,7 @@ namespace Templates.Test.Helpers { private const string _urls = "http://127.0.0.1:0;https://127.0.0.1:0"; - public const string DefaultFramework = "netcoreapp3.1"; + public const string DefaultFramework = "netcoreapp5.0"; public static bool IsCIEnvironment => typeof(Project).Assembly.GetCustomAttributes() .Any(a => a.Key == "ContinuousIntegrationBuild"); diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs index 2c34259592..f9de81d768 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -32,10 +32,12 @@ namespace Templates.Test.Helpers "Microsoft.DotNet.Web.ProjectTemplates.2.2", "Microsoft.DotNet.Web.ProjectTemplates.3.0", "Microsoft.DotNet.Web.ProjectTemplates.3.1", + "Microsoft.DotNet.Web.ProjectTemplates.5.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.1", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.2", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1", + "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates" }; @@ -90,7 +92,7 @@ namespace Templates.Test.Helpers /* * The templates are indexed by path, for example: - &USERPROFILE%\.templateengine\dotnetcli\v3.0.100-preview7-012821\packages\nunit3.dotnetnew.template.1.6.1.nupkg + &USERPROFILE%\.templateengine\dotnetcli\v5.0.100-alpha1-013788\packages\nunit3.dotnetnew.template.1.6.1.nupkg Templates: NUnit 3 Test Project (nunit) C# NUnit 3 Test Item (nunit-test) C# @@ -99,7 +101,7 @@ namespace Templates.Test.Helpers NUnit 3 Test Project (nunit) VB NUnit 3 Test Item (nunit-test) VB Uninstall Command: - dotnet new -u &USERPROFILE%\.templateengine\dotnetcli\v3.0.100-preview7-012821\packages\nunit3.dotnetnew.template.1.6.1.nupkg + dotnet new -u &USERPROFILE%\.templateengine\dotnetcli\v5.0.100-alpha1-013788\packages\nunit3.dotnetnew.template.1.6.1.nupkg * We don't want to construct this path so we'll rely on dotnet new --uninstall --help to construct the uninstall command. */ diff --git a/src/ProjectTemplates/test/IdentityUIPackageTest.cs b/src/ProjectTemplates/test/IdentityUIPackageTest.cs index 204c560859..80f5768c79 100644 --- a/src/ProjectTemplates/test/IdentityUIPackageTest.cs +++ b/src/ProjectTemplates/test/IdentityUIPackageTest.cs @@ -134,7 +134,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(packageOptions: packageOptions); diff --git a/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in b/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in index 6186fc2f15..9990532b1d 100644 --- a/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in +++ b/src/ProjectTemplates/test/Infrastructure/Directory.Build.props.in @@ -1,3 +1,6 @@ + + netcoreapp5.0 + diff --git a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in index 11b43cc0cb..e012ba42b7 100644 --- a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in +++ b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in @@ -5,8 +5,6 @@ $(MSBuildThisFileDirectory)runtimeconfig.norollforward.json - - ${MicrosoftNETCorePlatformsPackageVersion} diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 95f6ce784a..9fef0e00ff 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -44,11 +44,17 @@ namespace Templates.Test Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents); Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents); + // Avoid the F# compiler. See https://github.com/aspnet/AspNetCore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); @@ -116,7 +122,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj index b8797c1345..f755b3b8f2 100644 --- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj +++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj @@ -62,6 +62,7 @@ <_Parameter2>true + $([MSBuild]::NormalizePath('$(OutputPath)$(TestTemplateCreationFolder)')) @@ -79,7 +80,7 @@ <_Parameter1>ArtifactsLogDir <_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log')) - + <_Parameter1>ArtifactsNonShippingPackagesDir <_Parameter2>$(ArtifactsNonShippingPackagesDir) diff --git a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs index 8186ab4156..8a044fed5d 100644 --- a/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorClassLibraryTemplateTest.cs @@ -33,7 +33,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); @@ -52,7 +52,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 637d7179b5..d2e36aa31a 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -45,7 +45,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, createResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); @@ -115,7 +115,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs index 700349b773..df363915b8 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs @@ -66,17 +66,15 @@ namespace Templates.Test.SpaTemplateTest using var lintResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run lint"); Assert.True(0 == lintResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run lint", Project, lintResult)); - if (template == "react" || template == "reactredux") - { - using var testResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run test"); - Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult)); - } + var testcommand = "npm run test" + template == "angular" ? "-- --watch=false" : ""; + var testResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, testcommand); + Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult)); using var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. using var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index 8e08db9068..797cbfdc39 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -32,11 +32,17 @@ namespace Templates.Test var createResult = await Project.RunDotNetNewAsync("webapi", language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); + // Avoid the F# compiler. See https://github.com/aspnet/AspNetCore/issues/14022 + if (languageOverride != null) + { + return; + } + var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index fbe88be770..f4016a76af 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -32,7 +32,7 @@ namespace Templates.Test Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreapp3.1/publish and won't be affected by calling build + // The output from publish will go into bin/Release/netcoreapp5.0/publish and won't be affected by calling build // later, while the opposite is not true. var buildResult = await Project.RunDotNetBuildAsync(); diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index d934d8b545..618bb27a1f 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -1209,6 +1209,7 @@ "ClientApp/src/App.test.js", "ClientApp/src/index.js", "ClientApp/src/registerServiceWorker.js", + "ClientApp/.env", "ClientApp/.gitignore", "ClientApp/package-lock.json", "ClientApp/package.json", @@ -1251,6 +1252,7 @@ "ClientApp/src/index.tsx", "ClientApp/src/react-app-env.d.ts", "ClientApp/src/registerServiceWorker.ts", + "ClientApp/.env", "ClientApp/.eslintrc.json", "ClientApp/.gitignore", "ClientApp/package-lock.json", diff --git a/src/Security/Authentication/Certificate/src/README.md b/src/Security/Authentication/Certificate/src/README.md index 542131fdf1..b5654819e6 100644 --- a/src/Security/Authentication/Certificate/src/README.md +++ b/src/Security/Authentication/Certificate/src/README.md @@ -1,31 +1,22 @@ # Microsoft.AspNetCore.Authentication.Certificate -This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. -Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler -that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. +This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. -You **must** [configure your host](#hostConfiguration) for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using. +You **must** [configure your host](#configuring-your-host-to-require-certificates) for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using. ## Getting started -First acquire an HTTPS certificate, apply it and then [configure your host](#hostConfiguration) to require certificates. +First acquire an HTTPS certificate, apply it and then [configure your host](#configuring-your-host-to-require-certificates) to require certificates. -In your web application add a reference to the package, then in the `ConfigureServices` method in `startup.cs` call -`app.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).UseCertificateAuthentication(...);` with your options, -providing a delegate for `OnValidateCertificate` to validate the client certificate sent with requests and turn that information -into an `ClaimsPrincipal`, set it on the `context.Principal` property and call `context.Success()`. +In your web application add a reference to the package, then in the `ConfigureServices` method in `startup.cs` call `app.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).UseCertificateAuthentication(...);` with your options, providing a delegate for `OnValidateCertificate` to validate the client certificate sent with requests and turn that information into an `ClaimsPrincipal`, set it on the `context.Principal` property and call `context.Success()`. -If you change your scheme name in the options for the authentication handler you need to change the scheme name in -`AddAuthentication()` to ensure it's used on every request which ends in an endpoint that requires authorization. +If you change your scheme name in the options for the authentication handler you need to change the scheme name in `AddAuthentication()` to ensure it's used on every request which ends in an endpoint that requires authorization. -If authentication fails this handler will return a `403 (Forbidden)` response rather a `401 (Unauthorized)` as you -might expect - this is because the authentication should happen during the initial TLS connection - by the time it -reaches the handler it's too late, and there's no way to actually upgrade the connection from an anonymous connection -to one with a certificate. +If authentication fails this handler will return a `403 (Forbidden)` response rather a `401 (Unauthorized)` as you might expect - this is because the authentication should happen during the initial TLS connection - by the time it reaches the handler it's too late, and there's no way to actually upgrade the connection from an anonymous connection to one with a certificate. You must also add `app.UseAuthentication();` in the `Configure` method, otherwise nothing will ever get called. -For example; +For example: ```c# public void ConfigureServices(IServiceCollection services) @@ -47,25 +38,19 @@ In the sample above you can see the default way to add certificate authenticatio ## Configuring Certificate Validation -The `CertificateAuthenticationOptions` handler has some built in validations that are the minimium validations you should perform on -a certificate. Each of these settings are turned on by default. +The `CertificateAuthenticationOptions` handler has some built in validations that are the minimum validations you should perform on a certificate. Each of these settings are turned on by default. ### ValidateCertificateChain -This check validates that the issuer for the certificate is trusted by the application host OS. If -you are going to accept self-signed certificates you must disable this check. +This check validates that the issuer for the certificate is trusted by the application host OS. If you are going to accept self-signed certificates you must disable this check. ### ValidateCertificateUse -This check validates that the certificate presented by the client has the Client Authentication -extended key use, or no EKUs at all (as the specifications say if no EKU is specified then all EKUs -are valid). +This check validates that the certificate presented by the client has the Client Authentication extended key use, or no EKUs at all (as the specifications say if no EKU is specified then all EKUs are valid). ### ValidateValidityPeriod -This check validates that the certificate is within its validity period. As the handler runs on every -request this ensures that a certificate that was valid when it was presented has not expired during -its current session. +This check validates that the certificate is within its validity period. As the handler runs on every request this ensures that a certificate that was valid when it was presented has not expired during its current session. ### RevocationFlag @@ -73,24 +58,21 @@ A flag which specifies which certificates in the chain are checked for revocatio Revocation checks are only performed when the certificate is chained to a root certificate. -### RevocationMode +### RevocationMode A flag which specifies how revocation checks are performed. + Specifying an on-line check can result in a long delay while the certificate authority is contacted. Revocation checks are only performed when the certificate is chained to a root certificate. ### Can I configure my application to require a certificate only on certain paths? -Not possible, remember the certificate exchange is done that the start of the HTTPS conversation, -it's done by the host, not the application. Kestrel, IIS, Azure Web Apps don't have any configuration for -this sort of thing. +Not possible, remember the certificate exchange is done that the start of the HTTPS conversation, it's done by the host, not the application. Kestrel, IIS, Azure Web Apps don't have any configuration for this sort of thing. -# Handler events +## Handler events -The handler has two events, `OnAuthenticationFailed()`, which is called if an exception happens during authentication and allows you to react, and `OnValidateCertificate()` which is -called after certificate has been validated, passed validation, abut before the default principal has been created. This allows you to perform your own validation, for example -checking if the certificate is one your services knows about, and to construct your own principal. For example, +The handler has two events, `OnAuthenticationFailed()`, which is called if an exception happens during authentication and allows you to react, and `OnValidateCertificate()` which is called after certificate has been validated, passed validation, abut before the default principal has been created. This allows you to perform your own validation, for example checking if the certificate is one your services knows about, and to construct your own principal. For example: ```c# services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) @@ -117,8 +99,7 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem If you find the inbound certificate doesn't meet your extra validation call `context.Fail("failure Reason")` with a failure reason. -For real functionality you will probably want to call a service registered in DI which talks to a database or other type of -user store. You can grab your service by using the context passed into your delegates, like so +For real functionality you will probably want to call a service registered in DI which talks to a database or other type of user store. You can grab your service by using the context passed into your delegates, like so ```c# services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) @@ -130,7 +111,7 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem { var validationService = context.HttpContext.RequestServices.GetService(); - + if (validationService.ValidateCertificate(context.ClientCertificate)) { var claims = new[] @@ -141,17 +122,18 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); context.Success(); - } + } return Task.CompletedTask; } }; }); ``` + Note that conceptually the validation of the certification is an authorization concern, and putting a check on, for example, an issuer or thumbprint in an authorization policy rather than inside OnCertificateValidated() is perfectly acceptable. -## Configuring your host to require certificates +## Configuring your host to require certificates ### Kestrel @@ -170,12 +152,12 @@ public static IWebHost BuildWebHost(string[] args) }) .Build(); ``` -You must set the `ClientCertificateValidation` delegate to `CertificateValidator.DisableChannelValidation` in order to stop Kestrel using the default OS certificate validation routine and, -instead, letting the authentication handler perform the validation. + +You must set the `ClientCertificateValidation` delegate to `CertificateValidator.DisableChannelValidation` in order to stop Kestrel using the default OS certificate validation routine and, instead, letting the authentication handler perform the validation. ### IIS -In the IIS Manager +In the IIS Manager: 1. Select your Site in the Connections tab. 2. Double click the SSL Settings in the Features View window. @@ -185,9 +167,7 @@ In the IIS Manager ### Azure -See the [Azure documentation](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) -to configure Azure Web Apps then add the following to your application startup method, `Configure(IApplicationBuilder app)` add the -following line before the call to `app.UseAuthentication();` +See the [Azure documentation](https://docs.microsoft.com/azure/app-service/app-service-web-configure-tls-mutual-auth) to configure Azure Web Apps then add the following to your application startup method, `Configure(IApplicationBuilder app)` add the following line before the call to `app.UseAuthentication();`: ```c# app.UseCertificateHeaderForwarding(); @@ -195,18 +175,13 @@ app.UseCertificateHeaderForwarding(); ### Random custom web proxies -If you're using a proxy which isn't IIS or Azure's Web Apps Application Request Routing you will need to configure your proxy -to forward the certificate it received in an HTTP header. -In your application startup method, `Configure(IApplicationBuilder app)`, add the -following line before the call to `app.UseAuthentication();` +If you're using a proxy which isn't IIS or Azure's Web Apps Application Request Routing you will need to configure your proxy to forward the certificate it received in an HTTP header. In your application startup method, `Configure(IApplicationBuilder app)`, add the following line before the call to `app.UseAuthentication();`: ```c# app.UseCertificateForwarding(); ``` -You will also need to configure the Certificate Forwarding middleware to specify the header name. -In your service configuration method, `ConfigureServices(IServiceCollection services)` add -the following code to configure the header the forwarding middleware will build a certificate from; +You will also need to configure the Certificate Forwarding middleware to specify the header name. In your service configuration method, `ConfigureServices(IServiceCollection services)` add the following code to configure the header the forwarding middleware will build a certificate from: ```c# services.AddCertificateForwarding(options => @@ -215,9 +190,7 @@ services.AddCertificateForwarding(options => }); ``` -Finally, if your proxy is doing something weird to pass the header on, rather than base 64 encoding it -(looking at you nginx (╯°□°)╯︵ ┻━┻) you can override the converter option to be a func that will -perform the optional conversion, for example +Finally, if your proxy is doing something weird to pass the header on, rather than base 64 encoding it (looking at you nginx (╯°□°)╯︵ ┻━┻) you can override the converter option to be a func that will perform the optional conversion, for example ```c# services.AddCertificateForwarding(options => @@ -231,4 +204,3 @@ services.AddCertificateForwarding(options => } }); ``` - diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs index 8625e3f093..4e9737b509 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Authentication.OAuth; namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { /// - /// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference + /// See https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference /// public class MicrosoftChallengeProperties : OAuthChallengeProperties { diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md index e263a2c5f7..ec83949331 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md @@ -1,24 +1,24 @@ Cross Machine Tests -Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also neccisary for interop testing across OSs. Kerberos also requires domain controler SPN configuration so we can't test it on arbitrary test boxes. +Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also necessary for interop testing across OSs. Kerberos also requires domain controller SPN configuration so we can't test it on arbitrary test boxes. Test structure: - A remote test server with various endpoints with different authentication restrictions. -- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and desciption. +- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and description. - The CrossMachineTest class that drives the tests. It invokes the client app with the theory data and confirms the results. -We use these three components beceause it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. +We use these three components because it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. (Static) Environment Setup: - Warning, this environment can take a day to set up. That's why we want a static test environment that we can re-use. - Create a Windows server running DNS and Active Directory. Promote it to a domain controller. - Create an SPN on this machine for Windows -> Windows testing. `setspn -S "http/chrross-dc.crkerberos.com" -U administrator` - Future: Can we replace the domain controller with an AAD instance? We'd still want a second windows machine for Windows -> Windows testing, but AAD might be easier to configure. - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-getting-started - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-enable-kcd + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-getting-started + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-enable-kcd - Create another Windows machine and join it to the test domain. -- Create a Linux machine and joing it to the domain. Ubuntu 18.04 has been used in the past. +- Create a Linux machine and joining it to the domain. Ubuntu 18.04 has been used in the past. - https://www.safesquid.com/content-filtering/integrating-linux-host-windows-ad-kerberos-sso-authentication - Include an HTTP SPN diff --git a/src/Security/README.md b/src/Security/README.md index 0ba28c1e97..5ed702c4f2 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -3,9 +3,9 @@ ASP.NET Core Security Contains the security and authorization middlewares for ASP.NET Core. -A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/community). +A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/aspnet/core/security/authentication/community). -See the [ASP.NET Core security documentation](https://docs.microsoft.com/en-us/aspnet/core/security/). +See the [ASP.NET Core security documentation](https://docs.microsoft.com/aspnet/core/security/). ### Notes diff --git a/src/Security/samples/Identity.ExternalClaims/README.md b/src/Security/samples/Identity.ExternalClaims/README.md index 7a9141075d..70205c0367 100644 --- a/src/Security/samples/Identity.ExternalClaims/README.md +++ b/src/Security/samples/Identity.ExternalClaims/README.md @@ -4,7 +4,7 @@ AuthSamples.Identity.ExternalClaims Sample demonstrating copying over static and dynamic external claims from Google authentication during login: Steps: -1. Configure a google OAuth2 project. See https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?tabs=aspnetcore2x for basic setup using google logins. +1. Configure a google OAuth2 project. See https://docs.microsoft.com/aspnet/core/security/authentication/social/google-logins for basic setup using google logins. 2. Update Startup.cs AddGoogle()'s options with ClientId and ClientSecret for your google app. 3. Run the app and click on the MyClaims tab, this should trigger a redirect to login. 4. Login via the Google button, this should redirect you to google. diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index f829d6e92d..40efc20b79 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -2,16 +2,18 @@ // 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.Diagnostics.Contracts; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -74,6 +76,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys } var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0; + var serverAddressCopy = _serverAddresses.Addresses.ToList(); + _serverAddresses.Addresses.Clear(); if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent) { @@ -85,10 +89,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Options.UrlPrefixes.Clear(); } - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else if (_options.UrlPrefixes.Count > 0) { @@ -100,23 +101,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys _serverAddresses.Addresses.Clear(); } - foreach (var prefix in _options.UrlPrefixes) - { - _serverAddresses.Addresses.Add(prefix.FullPrefix); - } } else if (hostingUrlsPresent) { - foreach (var value in _serverAddresses.Addresses) - { - Listener.Options.UrlPrefixes.Add(value); - } + UpdateUrlPrefixes(serverAddressCopy); } else { LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default."); - _serverAddresses.Addresses.Add(Constants.DefaultServerAddress); Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress); } @@ -129,6 +122,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Start(); + // Update server addresses after we start listening as port 0 + // needs to be selected at the point of binding. + foreach (var prefix in _options.UrlPrefixes) + { + _serverAddresses.Addresses.Add(prefix.FullPrefix); + } + ActivateRequestProcessingLimits(); return Task.CompletedTask; @@ -142,6 +142,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + private void UpdateUrlPrefixes(IList serverAddressCopy) + { + foreach (var value in serverAddressCopy) + { + Listener.Options.UrlPrefixes.Add(value); + } + } + // The message pump. // When we start listening for the next request on one thread, we may need to be sure that the // completion continues on another thread as to not block the current request processing. diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index 8f33c7b678..87a5012641 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.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; @@ -73,7 +73,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys { LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix); CheckDisposed(); - var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0); if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 826cf629da..3d7d9a4088 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Protocol = handshake.Protocol; // The OS considers client and server TLS as different enum values. SslProtocols choose to combine those for some reason. // We need to fill in the client bits so the enum shows the expected protocol. - // https://docs.microsoft.com/en-us/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo + // https://docs.microsoft.com/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo // Compare to https://referencesource.microsoft.com/#System/net/System/Net/SecureProtocols/_SslState.cs,8905d1bf17729de3 #pragma warning disable CS0618 // Type or member is obsolete if ((Protocol & SslProtocols.Ssl2) != 0) diff --git a/src/Servers/HttpSys/src/UrlPrefixCollection.cs b/src/Servers/HttpSys/src/UrlPrefixCollection.cs index 92c50aea09..954b3d6d6f 100644 --- a/src/Servers/HttpSys/src/UrlPrefixCollection.cs +++ b/src/Servers/HttpSys/src/UrlPrefixCollection.cs @@ -1,8 +1,13 @@ -// 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; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { @@ -15,6 +20,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys private UrlGroup _urlGroup; private int _nextId = 1; + // Valid port range of 5000 - 48000. + private const int BasePort = 5000; + private const int MaxPortIndex = 43000; + private const int MaxRetries = 1000; + private static int NextPortIndex; + internal UrlPrefixCollection() { } @@ -138,10 +149,55 @@ namespace Microsoft.AspNetCore.Server.HttpSys { _urlGroup = urlGroup; // go through the uri list and register for each one of them - foreach (var pair in _prefixes) + // Call ToList to avoid modification when enumerating. + foreach (var pair in _prefixes.ToList()) { - // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. - _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + var urlPrefix = pair.Value; + if (urlPrefix.PortValue == 0) + { + if (urlPrefix.IsHttps) + { + throw new InvalidOperationException("Cannot bind to port 0 with https."); + } + + FindHttpPortUnsynchronized(pair.Key, urlPrefix); + } + else + { + // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. + _urlGroup.RegisterPrefix(pair.Value.FullPrefix, pair.Key); + } + } + } + } + + private void FindHttpPortUnsynchronized(int key, UrlPrefix urlPrefix) + { + for (var index = 0; index < MaxRetries; index++) + { + try + { + // Bit of complicated math to always try 3000 ports, starting from NextPortIndex + 5000, + // circling back around if we go above 8000 back to 5000, and so on. + var port = ((index + NextPortIndex) % MaxPortIndex) + BasePort; + + Debug.Assert(port >= 5000 || port < 8000); + + var newPrefix = UrlPrefix.Create(urlPrefix.Scheme, urlPrefix.Host, port, urlPrefix.Path); + _urlGroup.RegisterPrefix(newPrefix.FullPrefix, key); + _prefixes[key] = newPrefix; + + NextPortIndex += index + 1; + return; + } + catch (HttpSysException ex) + { + if ((ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ACCESS_DENIED + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SHARING_VIOLATION + && ex.ErrorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) || index == MaxRetries - 1) + { + throw; + } } } } @@ -159,4 +215,4 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } } -} \ No newline at end of file +} diff --git a/src/Servers/HttpSys/startvs.cmd b/src/Servers/HttpSys/startvs.cmd new file mode 100644 index 0000000000..94b00042d2 --- /dev/null +++ b/src/Servers/HttpSys/startvs.cmd @@ -0,0 +1,3 @@ +@ECHO OFF + +%~dp0..\..\..\startvs.cmd %~dp0HttpSysServer.sln diff --git a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs index 250ff9c9be..f9b4d81209 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/MessagePumpTests.cs @@ -114,7 +114,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); - Assert.Equal(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); + // Trailing slash is added when put in UrlPrefix. + Assert.StartsWith(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs index 8ba9e7b39a..a347ce427f 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Utilities.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.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -21,11 +22,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { // When tests projects are run in parallel, overlapping port ranges can cause a race condition when looking for free // ports during dynamic port allocation. - private const int BasePort = 5001; - private const int MaxPort = 8000; private const int BaseHttpsPort = 44300; private const int MaxHttpsPort = 44399; - private static int NextPort = BasePort; private static int NextHttpsPort = BaseHttpsPort; private static object PortLock = new object(); internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); @@ -84,39 +82,26 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IWebHost CreateDynamicHost(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); + + var builder = new WebHostBuilder() + .UseHttpSys(options => { - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + options.UrlPrefixes.Add(prefix); + configureOptions(options); + }) + .Configure(appBuilder => appBuilder.Run(app)); - var builder = new WebHostBuilder() - .UseHttpSys(options => - { - options.UrlPrefixes.Add(prefix); - configureOptions(options); - }) - .Configure(appBuilder => appBuilder.Run(app)); + var host = builder.Build(); - var host = builder.Build(); + host.Start(); + var options = host.Services.GetRequiredService>(); + prefix = options.Value.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); - try - { - host.Start(); - return host; - } - catch (HttpSysException) - { - } - - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + return host; } internal static MessagePump CreatePump() @@ -131,30 +116,17 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app) { - lock (PortLock) - { - while (NextPort < MaxPort) - { + var prefix = UrlPrefix.Create("http", "localhost", "0", basePath); - var port = NextPort++; - var prefix = UrlPrefix.Create("http", "localhost", port, basePath); - root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; - baseAddress = prefix.ToString(); + var server = CreatePump(configureOptions); + server.Features.Get().Addresses.Add(prefix.ToString()); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - var server = CreatePump(configureOptions); - server.Features.Get().Addresses.Add(baseAddress); - try - { - server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); - return server; - } - catch (HttpSysException) - { - } - } - NextPort = BasePort; - } - throw new Exception("Failed to locate a free port."); + prefix = server.Listener.Options.UrlPrefixes.First(); // Has new port + root = prefix.Scheme + "://" + prefix.Host + ":" + prefix.Port; + baseAddress = prefix.ToString(); + + return server; } internal static IServer CreateDynamicHttpsServer(out string baseAddress, RequestDelegate app) diff --git a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs index 8614ac36db..20d0f0713f 100644 --- a/src/Servers/HttpSys/test/Tests/UrlPrefixTests.cs +++ b/src/Servers/HttpSys/test/Tests/UrlPrefixTests.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; diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs index 6130cdf882..36520369a6 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/BasicAuthTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs index ccc4ea6458..6d39a56921 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs index 82df45fdbf..38f4dae818 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/CommonStartupTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs index 9a931f7d52..8e3251570b 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/HttpsTests.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index 5445bd24cc..d3aecfc29c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -172,7 +172,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAncmV2InProcess(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs index 699bd8b3ac..ffde811027 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes() .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs index ddc981d7af..df160cb44c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable); public static IEnumerable InvalidTestVariants diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs index deaf45f95a..2054011422 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.OutOfProcess public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllApplicationTypes(); [ConditionalTheory] diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs index a0fd00f869..70a7606e31 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedSitesFixture.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests RuntimeFlavor = RuntimeFlavor.CoreClr, RuntimeArchitecture = RuntimeArchitecture.x64, HostingModel = hostingModel, - TargetFramework = Tfm.NetCoreApp31 + TargetFramework = Tfm.NetCoreApp50 }); } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs index 645e595cbf..29d6f9c80c 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests { RuntimeArchitecture = RuntimeArchitecture.x64, RuntimeFlavor = RuntimeFlavor.CoreClr, - TargetFramework = Tfm.NetCoreApp31, + TargetFramework = Tfm.NetCoreApp50, HostingModel = HostingModel.InProcess, PublishApplicationBeforeDeployment = true, ApplicationPublisher = new PublishedApplicationPublisher(Helpers.GetInProcessTestSitesName()), diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs index f9716c2a26..e22f96dadc 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/WindowsAuthTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels(); diff --git a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs index 17f557a338..c63c777c80 100644 --- a/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs +++ b/src/Servers/IIS/IIS/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); [ConditionalTheory] [RequiresIIS(IISCapability.WindowsAuthentication)] diff --git a/src/Servers/IIS/IntegrationTesting.IIS/src/IISExpressDeployer.cs b/src/Servers/IIS/IntegrationTesting.IIS/src/IISExpressDeployer.cs index 4206ab8f22..288c11a278 100644 --- a/src/Servers/IIS/IntegrationTesting.IIS/src/IISExpressDeployer.cs +++ b/src/Servers/IIS/IntegrationTesting.IIS/src/IISExpressDeployer.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS // Start timer StartTimer(); - // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/netcoreapp3.1/ + // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/netcoreapp5.0/ // and contentRoot points to the project directory so you get things like static assets. // For a published app both point to the publish directory. var dllRoot = CheckIfPublishIsRequired(); diff --git a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs index 929a408778..16f7ab0fce 100644 --- a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs +++ b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs @@ -139,6 +139,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core BadHttpRequestException ex; switch (reason) { + case RequestRejectionReason.TlsOverHttpError: + ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason); + break; case RequestRejectionReason.InvalidRequestLine: ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason); break; diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 0f49dedc81..20af4f4a9c 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -617,4 +617,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l A new stream was refused because this connection has too many streams that haven't finished processing. This may happen if many streams are aborted but not yet cleaned up. - + + Detected a TLS handshake to an endpoint that does not have TLS enabled. + + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 2fe5dfdb36..ce63ec989f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private const byte ByteTab = (byte)'\t'; private const byte ByteQuestionMark = (byte)'?'; private const byte BytePercentage = (byte)'%'; + private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { @@ -415,9 +416,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return new Span(data, methodLength); } + private unsafe bool IsTlsHandshake(byte* data, int length) + { + const byte SslRecordTypeHandshake = (byte)0x16; + + // Make sure we can check at least for the existence of a TLS handshake - we check the first byte + // See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/ + + return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake); + } + [StackTraceHidden] private unsafe void RejectRequestLine(byte* requestLine, int length) - => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + { + // Check for incoming TLS handshake over HTTP + if (IsTlsHandshake(requestLine, length)) + { + throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length); + } + else + { + throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + } + } [StackTraceHidden] private unsafe void RejectRequestHeader(byte* headerLine, int length) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs index fce21b6210..23dc6c67c6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs @@ -1,10 +1,11 @@ -// 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. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal enum RequestRejectionReason { + TlsOverHttpError, UnrecognizedHTTPVersion, InvalidRequestLine, InvalidRequestHeader, diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 3ccdccef69..10756c0e80 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -204,27 +204,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 while (_isClosed == 0) { var result = await Input.ReadAsync(); - var readableBuffer = result.Buffer; - var consumed = readableBuffer.Start; - var examined = readableBuffer.Start; + var buffer = result.Buffer; // Call UpdateCompletedStreams() prior to frame processing in order to remove any streams that have exceded their drain timeouts. UpdateCompletedStreams(); try { - if (!readableBuffer.IsEmpty) + while (Http2FrameReader.TryReadFrame(ref buffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload)) { - if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload)) - { - Log.Http2FrameReceived(ConnectionId, _incomingFrame); - consumed = examined = framePayload.End; - await ProcessFrameAsync(application, framePayload); - } - else - { - examined = readableBuffer.End; - } + Log.Http2FrameReceived(ConnectionId, _incomingFrame); + await ProcessFrameAsync(application, framePayload); } if (result.IsCompleted) @@ -242,7 +232,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } finally { - Input.AdvanceTo(consumed, examined); + Input.AdvanceTo(buffer.Start, buffer.End); UpdateConnectionState(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs index 8437ad334a..ed4db88f0e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameReader.cs @@ -31,16 +31,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value. - public static bool ReadFrame(in ReadOnlySequence readableBuffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence framePayload) + public static bool TryReadFrame(ref ReadOnlySequence buffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence framePayload) { framePayload = ReadOnlySequence.Empty; - if (readableBuffer.Length < HeaderLength) + if (buffer.Length < HeaderLength) { return false; } - var headerSlice = readableBuffer.Slice(0, HeaderLength); + var headerSlice = buffer.Slice(0, HeaderLength); var header = headerSlice.ToSpan(); var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header); @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // Make sure the whole frame is buffered var frameLength = HeaderLength + payloadLength; - if (readableBuffer.Length < frameLength) + if (buffer.Length < frameLength) { return false; } @@ -61,10 +61,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 frame.Flags = header[FlagsOffset]; frame.StreamId = (int)Bitshifter.ReadUInt31BigEndian(header.Slice(StreamIdOffset)); - var extendedHeaderLength = ReadExtendedFields(frame, readableBuffer); + var extendedHeaderLength = ReadExtendedFields(frame, buffer); // The remaining payload minus the extra fields - framePayload = readableBuffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength); + framePayload = buffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength); + buffer = buffer.Slice(framePayload.End); return true; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 7555b9f223..1177504aa6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -239,7 +239,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } - public ValueTask WriteDataAsync(int streamId, StreamOutputFlowControl flowControl, in ReadOnlySequence data, bool endStream) + public ValueTask WriteDataAsync(int streamId, StreamOutputFlowControl flowControl, in ReadOnlySequence data, bool endStream, bool forceFlush) { // The Length property of a ReadOnlySequence can be expensive, so we cache the value. var dataLength = data.Length; @@ -261,7 +261,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // This cast is safe since if dataLength would overflow an int, it's guaranteed to be greater than the available flow control window. flowControl.Advance((int)dataLength); WriteDataUnsynchronized(streamId, data, dataLength, endStream); - return TimeFlushUnsynchronizedAsync(); + + if (forceFlush) + { + return TimeFlushUnsynchronizedAsync(); + } + + return default; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index 18adcc1a82..3bd9794e47 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -380,7 +380,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // Write any remaining content then write trailers if (readResult.Buffer.Length > 0) { - flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream: false); + // Only flush if required (i.e. content length exceeds flow control availability) + // Writing remaining content without flushing allows content and trailers to be sent in the same packet + await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream: false, forceFlush: false); } _stream.ResponseTrailers.SetReadOnly(); @@ -404,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { _stream.DecrementActiveClientStreamCount(); } - flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream); + flushResult = await _frameWriter.WriteDataAsync(_streamId, _flowControl, readResult.Buffer, endStream, forceFlush: true); } _pipeReader.AdvanceTo(readResult.Buffer.End); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs index fdd8ac7367..a9bf8d9424 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/BufferSegment.cs @@ -59,20 +59,16 @@ namespace System.IO.Pipelines AvailableMemory = arrayPoolBuffer; } - public void SetUnownedMemory(Memory memory) - { - AvailableMemory = memory; - } - public void ResetMemory() { if (_memoryOwner is IMemoryOwner owner) { owner.Dispose(); } - else if (_memoryOwner is byte[] array) + else { - ArrayPool.Shared.Return(array); + byte[] poolArray = (byte[])_memoryOwner; + ArrayPool.Shared.Return(poolArray); } // Order of below field clears is significant as it clears in a sequential order diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs index 3ab051c8c4..4e09b0a8a4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs @@ -341,8 +341,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW } else { - // We can't use the pool so allocate an array - newSegment.SetUnownedMemory(new byte[sizeHint]); + // We can't use the recommended pool so use the ArrayPool + newSegment.SetOwnedMemory(ArrayPool.Shared.Rent(sizeHint)); } _tailMemory = newSegment.AvailableMemory; diff --git a/src/Servers/Kestrel/Core/test/HttpParserTests.cs b/src/Servers/Kestrel/Core/test/HttpParserTests.cs index 7ce8587743..82d69d8b4d 100644 --- a/src/Servers/Kestrel/Core/test/HttpParserTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpParserTests.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; @@ -394,6 +394,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(buffer.End, examined); } + [Fact] + public void ParseRequestLineTlsOverHttp() + { + var parser = CreateParser(_nullTrace); + var buffer = ReadOnlySequenceFactory.CreateSegments(new byte[] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0xfc, 0x03, 0x03, 0x03, 0xca, 0xe0, 0xfd, 0x0a }); + + var requestHandler = new RequestHandler(); + + var badHttpRequestException = Assert.Throws(() => + { + parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined); + }); + + Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest); + Assert.Equal(RequestRejectionReason.TlsOverHttpError, badHttpRequestException.Reason); + } + [Fact] public void ParseHeadersWithGratuitouslySplitBuffers() { diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index ccdb774674..600d674d98 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -62,6 +62,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } + // Check if EndPoint is a FileHandleEndpoint before attempting to access EndPoint.AddressFamily + // since that will throw an NotImplementedException. + if (EndPoint is FileHandleEndPoint) + { + throw new NotSupportedException(SocketsStrings.FileHandleEndPointNotSupported); + } + Socket listenSocket; // Unix domain sockets are unspecified diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx index 52b26c66bc..5f1475a1cf 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx @@ -117,10 +117,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The Socket transport does not support binding to file handles. Consider using the libuv transport instead. + Only ListenType.IPEndPoint is supported by the Socket Transport. https://go.microsoft.com/fwlink/?linkid=874850 Transport is already bound. - \ No newline at end of file + diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs index 7ddb4deb26..47e3288be5 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs @@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 await stopTask.DefaultTimeout(); } - Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished in")); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished ")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closing.")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closed. The last processed stream ID was 1.")); } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index d3fa68a1bd..14cea7f48a 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; @@ -755,6 +756,176 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests }; testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + for (var i = 0; i < chunkCount; i++) + { + await context.Response.BodyWriter.WriteAsync(new Memory(chunkData, 0, chunkData.Length), context.RequestAborted); + } + + appFuncCompleted.SetResult(null); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + // Close the connection with the last request so AssertStreamCompleted actually completes. + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + // Make sure consuming a single chunk exceeds the 2 second timeout. + var targetBytesPerSecond = chunkSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 33_553_537, targetBytesPerSecond); + await appFuncCompleted.Task.DefaultTimeout(); + + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + [CollectDump] + public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() + { + var headerSize = 1024 * 1024; // 1 MB for each header value + var headerCount = 64; // 64 MB of headers per response + var requestCount = 4; // Minimum of 256 MB of total response headers + var headerValue = new string('a', headerSize); + var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); + + var requestAborted = false; + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + + async Task App(HttpContext context) + { + context.RequestAborted.Register(() => + { + requestAborted = true; + }); + + context.Response.Headers[$"X-Custom-Header"] = headerStringValues; + context.Response.ContentLength = 0; + + await context.Response.BodyWriter.FlushAsync(); + } + + using (var server = new TestServer(App, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + for (var i = 0; i < requestCount - 1; i++) + { + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + } + + await connection.Send( + "GET / HTTP/1.1", + "Host:", + "", + ""); + + await connection.Receive( + "HTTP/1.1 200 OK", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); + + var minResponseSize = headerSize * headerCount; + var minTotalOutputSize = requestCount * minResponseSize; + + // Make sure consuming a single set of response headers exceeds the 2 second timeout. + var targetBytesPerSecond = minResponseSize / 4; + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic itself changes. + await AssertBytesReceivedAtTargetRate(connection.Stream, expectedBytes: 268_439_596, targetBytesPerSecond); + connection.ShutdownSend(); + await connection.WaitForConnectionClose(); + } + + await server.StopAsync(); + } + + mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); + mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); + Assert.False(requestAborted); + } + + [Fact] + [Flaky("https://github.com/aspnet/AspNetCore/issues/13219", FlakyOn.AzP.Linux, FlakyOn.Helix.All)] + public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowDataRate() + { + var chunkSize = 64 * 128 * 1024; + var chunkCount = 4; + var chunkData = new byte[chunkSize]; + + var requestAborted = false; + var appFuncCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var mockKestrelTrace = new Mock(); + + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) + { + ServerOptions = + { + Limits = + { + MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) + } + } + }; + + testContext.InitializeHeartbeat(); + var dateHeaderValueManager = new DateHeaderValueManager(); + dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -785,11 +956,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests "", ""); - var minTotalOutputSize = chunkCount * chunkSize; + await connection.Receive( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {dateHeaderValueManager.GetDateHeaderValues().String}"); // Make sure consuming a single chunk exceeds the 2 second timeout. var targetBytesPerSecond = chunkSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); + + // expectedBytes was determined by manual testing. A constant Date header is used, so this shouldn't change unless + // the response header writing logic or response body chunking logic itself changes. + await AssertStreamCompletedAtTargetRate(connection.Stream, expectedBytes: 33_553_556, targetBytesPerSecond); await appFuncCompleted.Task.DefaultTimeout(); } await server.StopAsync(); @@ -800,87 +977,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.False(requestAborted); } - private bool ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeadersRetryPredicate(Exception e) - => e is IOException && e.Message.Contains("Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request"); - - [Fact] - [Flaky("https://github.com/dotnet/corefx/issues/30691", FlakyOn.AzP.Windows)] - [CollectDump] - public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLargeResponseHeaders() - { - var headerSize = 1024 * 1024; // 1 MB for each header value - var headerCount = 64; // 64 MB of headers per response - var requestCount = 4; // Minimum of 256 MB of total response headers - var headerValue = new string('a', headerSize); - var headerStringValues = new StringValues(Enumerable.Repeat(headerValue, headerCount).ToArray()); - - var requestAborted = false; - var mockKestrelTrace = new Mock(); - - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) - { - ServerOptions = - { - Limits = - { - MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(2)) - } - } - }; - - testContext.InitializeHeartbeat(); - - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); - - async Task App(HttpContext context) - { - context.RequestAborted.Register(() => - { - requestAborted = true; - }); - - context.Response.Headers[$"X-Custom-Header"] = headerStringValues; - context.Response.ContentLength = 0; - - await context.Response.BodyWriter.FlushAsync(); - } - - using (var server = new TestServer(App, testContext, listenOptions)) - { - using (var connection = server.CreateConnection()) - { - for (var i = 0; i < requestCount - 1; i++) - { - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "", - ""); - } - - // Close the connection with the last request so AssertStreamCompleted actually completes. - await connection.Send( - "GET / HTTP/1.1", - "Host:", - "Connection: close", - "", - ""); - - var responseSize = headerSize * headerCount; - var minTotalOutputSize = requestCount * responseSize; - - // Make sure consuming a single set of response headers exceeds the 2 second timeout. - var targetBytesPerSecond = responseSize / 4; - await AssertStreamCompleted(connection.Stream, minTotalOutputSize, targetBytesPerSecond); - } - await server.StopAsync(); - } - - mockKestrelTrace.Verify(t => t.ResponseMinimumDataRateNotSatisfied(It.IsAny(), It.IsAny()), Times.Never()); - mockKestrelTrace.Verify(t => t.ConnectionStop(It.IsAny()), Times.Once()); - Assert.False(requestAborted); - } - private async Task AssertStreamAborted(Stream stream, int totalBytes) { var receiveBuffer = new byte[64 * 1024]; @@ -908,7 +1004,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests Assert.True(totalReceived < totalBytes, $"{nameof(AssertStreamAborted)} Stream completed successfully."); } - private async Task AssertStreamCompleted(Stream stream, long minimumBytes, int targetBytesPerSecond) + private async Task AssertBytesReceivedAtTargetRate(Stream stream, int expectedBytes, int targetBytesPerSecond) + { + var receiveBuffer = new byte[64 * 1024]; + var totalReceived = 0; + var startTime = DateTimeOffset.UtcNow; + + do + { + var received = await stream.ReadAsync(receiveBuffer, 0, Math.Min(receiveBuffer.Length, expectedBytes - totalReceived)); + + Assert.NotEqual(0, received); + + totalReceived += received; + + var expectedTimeElapsed = TimeSpan.FromSeconds(totalReceived / targetBytesPerSecond); + var timeElapsed = DateTimeOffset.UtcNow - startTime; + if (timeElapsed < expectedTimeElapsed) + { + await Task.Delay(expectedTimeElapsed - timeElapsed); + } + } while (totalReceived < expectedBytes); + } + + private async Task AssertStreamCompletedAtTargetRate(Stream stream, long expectedBytes, int targetBytesPerSecond) { var receiveBuffer = new byte[64 * 1024]; var received = 0; @@ -928,7 +1047,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } while (received > 0); - Assert.True(totalReceived >= minimumBytes, $"{nameof(AssertStreamCompleted)} Stream aborted prematurely."); + Assert.Equal(expectedBytes, totalReceived); } public static TheoryData NullHeaderData diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index 4937000db9..00f8f3b829 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -2044,6 +2044,127 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Contains(CoreStrings.HPackErrorNotEnoughBuffer, message.Exception.Message); } + [Fact] + public async Task ResponseTrailers_WithLargeUnflushedData_DataExceedsFlowControlAvailableAndNotSentWithTrailers() + { + const int windowSize = (int)Http2PeerSettings.DefaultMaxFrameSize; + _clientSettings.InitialWindowSize = windowSize; + + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(async context => + { + await context.Response.StartAsync(); + + // Body exceeds flow control available and requires the client to allow more + // data via updating the window + context.Response.BodyWriter.GetMemory(windowSize + 1); + context.Response.BodyWriter.Advance(windowSize + 1); + + context.Response.AppendTrailer("CustomName", "Custom Value"); + }).DefaultTimeout(); + + await StartStreamAsync(1, headers, endStream: true).DefaultTimeout(); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1).DefaultTimeout(); + + var dataTask = ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1).DefaultTimeout(); + + // Reading final frame of data requires window update + // Verify this data task is waiting on window update + Assert.False(dataTask.IsCompletedSuccessfully); + + await SendWindowUpdateAsync(1, 1); + + await dataTask; + + var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1).DefaultTimeout(); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout(); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + _decodedHeaders.Clear(); + _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); + } + + [Fact] + public async Task ResponseTrailers_WithUnflushedData_DataSentWithTrailers() + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + }; + await InitializeConnectionAsync(async context => + { + await context.Response.StartAsync(); + + var s = context.Response.BodyWriter.GetMemory(1); + s.Span[0] = byte.MaxValue; + context.Response.BodyWriter.Advance(1); + + context.Response.AppendTrailer("CustomName", "Custom Value"); + }); + + await StartStreamAsync(1, headers, endStream: true); + + var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 37, + withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, + withStreamId: 1); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 1, + withFlags: (byte)Http2DataFrameFlags.NONE, + withStreamId: 1); + + var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS, + withLength: 25, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM), + withStreamId: 1); + + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); + + _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this); + + Assert.Equal(2, _decodedHeaders.Count); + Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", _decodedHeaders[HeaderNames.Status]); + + _decodedHeaders.Clear(); + _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this); + + Assert.Single(_decodedHeaders); + Assert.Equal("Custom Value", _decodedHeaders["CustomName"]); + } + [Fact] public async Task ApplicationException_BeforeFirstWrite_Sends500() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 137ea743b7..24e4e59228 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -1112,12 +1112,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var buffer = result.Buffer; var consumed = buffer.Start; var examined = buffer.Start; + var copyBuffer = buffer; try { Assert.True(buffer.Length > 0); - if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out var framePayload)) + if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload)) { consumed = examined = framePayload.End; frame.Payload = framePayload.ToArray(); @@ -1135,7 +1136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } finally { - _bytesReceived += buffer.Slice(buffer.Start, consumed).Length; + _bytesReceived += copyBuffer.Slice(copyBuffer.Start, consumed).Length; _pair.Application.Input.AdvanceTo(consumed, examined); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index f52e83eb62..1c78af53bf 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 try { - if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out var framePayload)) + if (Http2FrameReader.TryReadFrame(ref buffer, frame, 16_384, out var framePayload)) { consumed = examined = framePayload.End; return frame; diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs new file mode 100644 index 0000000000..2ff92f497f --- /dev/null +++ b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Sockets.BindTests +{ + public class SocketTransportFactoryTests + { + [Fact] + public async Task ThrowsNotSupportedExceptionWhenBindingToFileHandleEndPoint() + { + var socketTransportFactory = new SocketTransportFactory(Options.Create(new SocketTransportOptions()), Mock.Of()); + await Assert.ThrowsAsync(async () => await socketTransportFactory.BindAsync(new FileHandleEndPoint(0, FileHandleType.Auto))); + } + } +} + diff --git a/src/Servers/test/FunctionalTests/HelloWorldTest.cs b/src/Servers/test/FunctionalTests/HelloWorldTest.cs index e047288908..cf20b9d617 100644 --- a/src/Servers/test/FunctionalTests/HelloWorldTest.cs +++ b/src/Servers/test/FunctionalTests/HelloWorldTest.cs @@ -21,7 +21,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithApplicationTypes(ApplicationType.Portable) .WithAllHostingModels() .WithAllArchitectures(); diff --git a/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs b/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs index 330586eafa..75ef46207a 100644 --- a/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs +++ b/src/Servers/test/FunctionalTests/NtlmAuthenticationTest.cs @@ -22,7 +22,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys, ServerType.Kestrel) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] diff --git a/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs b/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs index 4f4af0b3e0..e441343521 100644 --- a/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseCompressionTests.cs @@ -33,7 +33,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix NoCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -45,7 +45,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix HostCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Nginx) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -57,7 +57,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix AppCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.HttpSys) // No pass-through compression for nginx - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -69,7 +69,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix HostAndAppCompressionTestVariants => TestMatrix.ForServers(ServerType.IISExpress, ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] diff --git a/src/Servers/test/FunctionalTests/ResponseTests.cs b/src/Servers/test/FunctionalTests/ResponseTests.cs index 169132f30b..904ec52b7a 100644 --- a/src/Servers/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/test/FunctionalTests/ResponseTests.cs @@ -26,7 +26,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix TestVariants => TestMatrix.ForServers(/* ServerType.IISExpress, https://github.com/aspnet/AspNetCore/issues/6168, */ ServerType.Kestrel, ServerType.Nginx, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31) + .WithTfms(Tfm.NetCoreApp50) .WithAllHostingModels(); [ConditionalTheory] @@ -52,7 +52,7 @@ namespace ServerComparison.FunctionalTests public static TestMatrix SelfhostTestVariants => TestMatrix.ForServers(ServerType.Kestrel, ServerType.HttpSys) - .WithTfms(Tfm.NetCoreApp31); + .WithTfms(Tfm.NetCoreApp50); // Connection Close tests do not work through reverse proxies [ConditionalTheory] diff --git a/src/Shared/E2ETesting/BrowserFixture.cs b/src/Shared/E2ETesting/BrowserFixture.cs index 0fa652994f..a453a2d7be 100644 --- a/src/Shared/E2ETesting/BrowserFixture.cs +++ b/src/Shared/E2ETesting/BrowserFixture.cs @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.E2ETesting // To prevent this we let the client attempt several times to connect to the server, increasing // the max allowed timeout for a command on each attempt linearly. // This can also be caused if many tests are running concurrently, we might want to manage - // chrome and chromedriver instances more aggresively if we have to. + // chrome and chromedriver instances more aggressively if we have to. // Additionally, if we think the selenium server has become irresponsive, we could spin up // replace the current selenium server instance and let a new instance take over for the // remaining tests. diff --git a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs index d62b3ffb00..91f80afb2b 100644 --- a/src/Shared/E2ETesting/SeleniumStandaloneServer.cs +++ b/src/Shared/E2ETesting/SeleniumStandaloneServer.cs @@ -191,7 +191,7 @@ Captured output lines: private static Process StartSentinelProcess(Process process, string sentinelFile, int timeout) { - // This sentinel process will start and will kill any roge selenium server that want' torn down + // This sentinel process will start and will kill any rouge selenium server that want' torn down // via normal means. var psi = new ProcessStartInfo { diff --git a/src/Shared/ErrorPage/GeneratePage.ps1 b/src/Shared/ErrorPage/GeneratePage.ps1 index 8bc0f2c07c..94e6746169 100644 --- a/src/Shared/ErrorPage/GeneratePage.ps1 +++ b/src/Shared/ErrorPage/GeneratePage.ps1 @@ -2,7 +2,7 @@ param( [Parameter(Mandatory = $true)][string]$ToolingRepoPath ) -$ToolPath = Join-Path $ToolingRepoPath "artifacts\bin\RazorPageGenerator\Debug\netcoreapp3.1\dotnet-razorpagegenerator.exe" +$ToolPath = Join-Path $ToolingRepoPath "artifacts\bin\RazorPageGenerator\Debug\netcoreapp5.0\dotnet-razorpagegenerator.exe" if (!(Test-Path $ToolPath)) { throw "Unable to find razor page generator tool at $ToolPath" diff --git a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs index 4483cbc306..4316e020e1 100644 --- a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs +++ b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.HttpSys.Internal internal static class ErrorCodes { internal const uint ERROR_SUCCESS = 0; + internal const uint ERROR_ACCESS_DENIED = 5; + internal const uint ERROR_SHARING_VIOLATION = 32; internal const uint ERROR_HANDLE_EOF = 38; internal const uint ERROR_NOT_SUPPORTED = 50; internal const uint ERROR_INVALID_PARAMETER = 87; diff --git a/src/SignalR/README.md b/src/SignalR/README.md index 084b5fbf71..80a69f98e7 100644 --- a/src/SignalR/README.md +++ b/src/SignalR/README.md @@ -7,7 +7,7 @@ You can watch an introductory presentation here - [ASP.NET Core SignalR: Build 2 ## Documentation -Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1) section of the ASP.NET Core Documentation site. +Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/aspnet/core/signalr/introduction) section of the ASP.NET Core Documentation site. ## TypeScript Version diff --git a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs index a361a4ed03..0635cd850a 100644 --- a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs +++ b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { - public partial class HubConnection + public partial class HubConnection : System.IAsyncDisposable { public static readonly System.TimeSpan DefaultHandshakeTimeout; public static readonly System.TimeSpan DefaultKeepAliveInterval; @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public event System.Func Reconnected { add { } remove { } } public event System.Func Reconnecting { add { } remove { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task InvokeCoreAsync(string methodName, System.Type returnType, object[] args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.IDisposable On(string methodName, System.Type[] parameterTypes, System.Func handler, object state) { throw null; } diff --git a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs index a361a4ed03..0635cd850a 100644 --- a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs +++ b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { - public partial class HubConnection + public partial class HubConnection : System.IAsyncDisposable { public static readonly System.TimeSpan DefaultHandshakeTimeout; public static readonly System.TimeSpan DefaultKeepAliveInterval; @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public event System.Func Reconnected { add { } remove { } } public event System.Func Reconnecting { add { } remove { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task InvokeCoreAsync(string methodName, System.Type returnType, object[] args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.IDisposable On(string methodName, System.Type[] parameterTypes, System.Func handler, object state) { throw null; } diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index 5e04bd309a..576e69326f 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR.Client { @@ -34,7 +33,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// Before hub methods can be invoked the connection must be started using . /// Clean up a connection using or . /// - public partial class HubConnection + public partial class HubConnection : IAsyncDisposable { public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromSeconds(30); // Server ping rate is 15 sec, this is 2 times that. public static readonly TimeSpan DefaultHandshakeTimeout = TimeSpan.FromSeconds(15); @@ -292,8 +291,8 @@ namespace Microsoft.AspNetCore.SignalR.Client /// /// Disposes the . /// - /// A that represents the asynchronous dispose. - public async Task DisposeAsync() + /// A that represents the asynchronous dispose. + public async ValueTask DisposeAsync() { if (!_disposed) { @@ -504,8 +503,16 @@ namespace Microsoft.AspNetCore.SignalR.Client if (disposing) { - (_serviceProvider as IDisposable)?.Dispose(); + // Must set this before calling DisposeAsync because the service provider has a reference to the HubConnection and will try to dispose it again _disposed = true; + if (_serviceProvider is IAsyncDisposable asyncDispose) + { + await asyncDispose.DisposeAsync(); + } + else + { + (_serviceProvider as IDisposable)?.Dispose(); + } } } finally @@ -532,7 +539,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// public IAsyncEnumerable StreamAsyncCore(string methodName, object[] args, CancellationToken cancellationToken = default) { - var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) : new CancellationTokenSource(); + var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default) : new CancellationTokenSource(); var stream = CastIAsyncEnumerable(methodName, args, cts); var cancelableStream = AsyncEnumerableAdapters.MakeCancelableTypedAsyncEnumerable(stream, cts); return cancelableStream; diff --git a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj index df54c48fec..e287d4c869 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj +++ b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 14e9d78e5b..5d40dcaf70 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -114,6 +114,71 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } + [Fact] + public async Task ServerRejectsClientWithOldProtocol() + { + bool ExpectedError(WriteContext writeContext) + { + return writeContext.LoggerName == typeof(HttpConnection).FullName && + writeContext.EventId.Name == "ErrorWithNegotiation"; + } + + var protocol = HubProtocols["json"]; + using (StartServer(out var server, ExpectedError)) + { + var connectionBuilder = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/negotiateProtocolVersion12", HttpTransportType.LongPolling); + connectionBuilder.Services.AddSingleton(protocol); + + var connection = connectionBuilder.Build(); + + try + { + var ex = await Assert.ThrowsAnyAsync(() => connection.StartAsync()).OrTimeout(); + Assert.Equal("The client requested version '1', but the server does not support this version.", ex.Message); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await connection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task ClientCanConnectToServerWithLowerMinimumProtocol() + { + var protocol = HubProtocols["json"]; + using (StartServer(out var server)) + { + var connectionBuilder = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/negotiateProtocolVersionNegative", HttpTransportType.LongPolling); + connectionBuilder.Services.AddSingleton(protocol); + + var connection = connectionBuilder.Build(); + + try + { + await connection.StartAsync().OrTimeout(); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await connection.DisposeAsync().OrTimeout(); + } + } + } + [Theory] [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task CanSendAndReceiveMessage(string protocolName, HttpTransportType transportType, string path) @@ -1414,6 +1479,118 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } + [Fact] + public async Task UserAgentIsSet() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["X-test"] = "42"; + options.Headers["X-42"] = "test"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.StartsWith("Microsoft SignalR/", userAgent); + + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + + Assert.Contains($"{majorVersion}.{minorVersion}", userAgent); + + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeCleared() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = ""; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Null(userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeSet() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = "User Value"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Equal("User Value", userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + [ConditionalFact] [WebSocketsSupportedCondition] public async Task WebSocketOptionsAreApplied() diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs index 1d7dbd6718..4cbc35c510 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/Startup.cs @@ -69,6 +69,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests endpoints.MapHub("/default-nowebsockets", options => options.Transports = HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents); + endpoints.MapHub("/negotiateProtocolVersion12", options => + { + options.MinimumProtocolVersion = 12; + }); + + endpoints.MapHub("/negotiateProtocolVersionNegative", options => + { + options.MinimumProtocolVersion = -1; + }); + endpoints.MapGet("/generateJwtToken", context => { return context.Response.WriteAsync(GenerateJwtToken()); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs index fa95fbc83b..e04e82a2b5 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs @@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests var httpHandler = new TestHttpMessageHandler(); var connectResponseTcs = new TaskCompletionSource(); - httpHandler.OnGet("/?id=00000000-0000-0000-0000-000000000000", async (_, __) => + httpHandler.OnGet("/?negotiateVersion=1&id=00000000-0000-0000-0000-000000000000", async (_, __) => { await connectResponseTcs.Task; return ResponseUtils.CreateResponse(HttpStatusCode.Accepted); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs index 348e33cebf..5abbde0313 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Negotiate.cs @@ -36,6 +36,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests return RunInvalidNegotiateResponseTest(ResponseUtils.CreateNegotiationContent(connectionId: string.Empty), "Invalid connection id."); } + [Fact] + public Task NegotiateResponseWithNegotiateVersionRequiresConnectionToken() + { + return RunInvalidNegotiateResponseTest(ResponseUtils.CreateNegotiationContent(negotiateVersion: 1, connectionToken: null), "Invalid negotiation response received."); + } + [Fact] public Task ConnectionCannotBeStartedIfNoCommonTransportsBetweenClientAndServer() { @@ -50,12 +56,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } [Theory] - [InlineData("http://fakeuri.org/", "http://fakeuri.org/negotiate")] - [InlineData("http://fakeuri.org/?q=1/0", "http://fakeuri.org/negotiate?q=1/0")] - [InlineData("http://fakeuri.org?q=1/0", "http://fakeuri.org/negotiate?q=1/0")] - [InlineData("http://fakeuri.org/endpoint", "http://fakeuri.org/endpoint/negotiate")] - [InlineData("http://fakeuri.org/endpoint/", "http://fakeuri.org/endpoint/negotiate")] - [InlineData("http://fakeuri.org/endpoint?q=1/0", "http://fakeuri.org/endpoint/negotiate?q=1/0")] + [InlineData("http://fakeuri.org/", "http://fakeuri.org/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/?q=1/0", "http://fakeuri.org/negotiate?q=1/0&negotiateVersion=1")] + [InlineData("http://fakeuri.org?q=1/0", "http://fakeuri.org/negotiate?q=1/0&negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint", "http://fakeuri.org/endpoint/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint/", "http://fakeuri.org/endpoint/negotiate?negotiateVersion=1")] + [InlineData("http://fakeuri.org/endpoint?q=1/0", "http://fakeuri.org/endpoint/negotiate?q=1/0&negotiateVersion=1")] public async Task CorrectlyHandlesQueryStringWhenAppendingNegotiateToUrl(string requestedUrl, string expectedNegotiate) { var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); @@ -119,6 +125,124 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); } + [Fact] + public async Task NegotiateCanHaveNewFields() + { + string connectionId = null; + + var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); + testHttpHandler.OnNegotiate((request, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, + JsonConvert.SerializeObject(new + { + connectionId = "0rge0d00-0040-0030-0r00-000q00r00e00", + availableTransports = new object[] + { + new + { + transport = "LongPolling", + transferFormats = new[] { "Text" } + }, + }, + newField = "ignore this", + }))); + testHttpHandler.OnLongPoll(cancellationToken => ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + testHttpHandler.OnLongPollDelete((token) => ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); + + using (var noErrorScope = new VerifyNoErrorsScope()) + { + await WithConnectionAsync( + CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory), + async (connection) => + { + await connection.StartAsync().OrTimeout(); + connectionId = connection.ConnectionId; + }); + } + + Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); + } + + [Fact] + public async Task ConnectionIdGetsSetWithNegotiateProtocolGreaterThanZero() + { + string connectionId = null; + + var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); + testHttpHandler.OnNegotiate((request, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, + JsonConvert.SerializeObject(new + { + connectionId = "0rge0d00-0040-0030-0r00-000q00r00e00", + negotiateVersion = 1, + connectionToken = "different-id", + availableTransports = new object[] + { + new + { + transport = "LongPolling", + transferFormats = new[] { "Text" } + }, + }, + newField = "ignore this", + }))); + testHttpHandler.OnLongPoll(cancellationToken => ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + testHttpHandler.OnLongPollDelete((token) => ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); + + using (var noErrorScope = new VerifyNoErrorsScope()) + { + await WithConnectionAsync( + CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory), + async (connection) => + { + await connection.StartAsync().OrTimeout(); + connectionId = connection.ConnectionId; + }); + } + + Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/?negotiateVersion=1&id=different-id", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + } + + [Fact] + public async Task ConnectionTokenFieldIsIgnoredForNegotiateIdLessThanOne() + { + string connectionId = null; + + var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false); + testHttpHandler.OnNegotiate((request, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, + JsonConvert.SerializeObject(new + { + connectionId = "0rge0d00-0040-0030-0r00-000q00r00e00", + connectionToken = "different-id", + availableTransports = new object[] + { + new + { + transport = "LongPolling", + transferFormats = new[] { "Text" } + }, + }, + newField = "ignore this", + }))); + testHttpHandler.OnLongPoll(cancellationToken => ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + testHttpHandler.OnLongPollDelete((token) => ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); + + using (var noErrorScope = new VerifyNoErrorsScope()) + { + await WithConnectionAsync( + CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory), + async (connection) => + { + await connection.StartAsync().OrTimeout(); + connectionId = connection.ConnectionId; + }); + } + + Assert.Equal("0rge0d00-0040-0030-0r00-000q00r00e00", connectionId); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + } + [Fact] public async Task NegotiateThatReturnsUrlGetFollowed() { @@ -172,10 +296,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } - Assert.Equal("http://fakeuri.org/negotiate", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat/negotiate", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); Assert.Equal(5, testHttpHandler.ReceivedRequests.Count); } @@ -278,10 +402,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } - Assert.Equal("http://fakeuri.org/negotiate", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat/negotiate", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); - Assert.Equal("https://another.domain.url/chat?id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); + Assert.Equal("http://fakeuri.org/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[0].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat/negotiate?negotiateVersion=1", testHttpHandler.ReceivedRequests[1].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[2].RequestUri.ToString()); + Assert.Equal("https://another.domain.url/chat?negotiateVersion=1&id=0rge0d00-0040-0030-0r00-000q00r00e00", testHttpHandler.ReceivedRequests[3].RequestUri.ToString()); // Delete request Assert.Equal(5, testHttpHandler.ReceivedRequests.Count); } diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs index 142e40546c..0244af0afd 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs @@ -3,6 +3,7 @@ using System; using System.IO.Pipelines; +using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -113,16 +114,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests testHttpHandler.OnRequest(async (request, next, token) => { - var userAgentHeaderCollection = request.Headers.UserAgent; - var userAgentHeader = Assert.Single(userAgentHeaderCollection); - Assert.Equal("Microsoft.AspNetCore.Http.Connections.Client", userAgentHeader.Product.Name); + var userAgentHeader = request.Headers.UserAgent.ToString(); + + Assert.NotNull(userAgentHeader); + Assert.StartsWith("Microsoft SignalR/", userAgentHeader); // user agent version should come from version embedded in assembly metadata var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttribute(); - Assert.Equal(assemblyVersion.InformationalVersion, userAgentHeader.Product.Version); + Assert.Contains(assemblyVersion.InformationalVersion, userAgentHeader); requestsExecuted = true; diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs index 2c9df93cb8..b303e71ddd 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs @@ -414,6 +414,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } } + [Fact] + public async Task CanAwaitUsingHubConnection() + { + using (StartVerifiableLog()) + { + var connection = new TestConnection(); + await using var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory); + await hubConnection.StartAsync().OrTimeout(); + } + } + private class SampleObject { public SampleObject(string foo, int bar) diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/ResponseUtils.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/ResponseUtils.cs index 58d5fbb53c..33dddf6aab 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/ResponseUtils.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/ResponseUtils.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } public static string CreateNegotiationContent(string connectionId = "00000000-0000-0000-0000-000000000000", - HttpTransportType? transportTypes = null) + HttpTransportType? transportTypes = null, string connectionToken = "connection-token", int negotiateVersion = 0) { var availableTransports = new List(); @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } - return JsonConvert.SerializeObject(new { connectionId, availableTransports }); + return JsonConvert.SerializeObject(new { connectionId, availableTransports, connectionToken, negotiateVersion }); } } } diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs index 06d05da7f5..36596d3236 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TestHttpMessageHandler.cs @@ -1,3 +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.Net; @@ -117,7 +120,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); testHttpMessageHandler.OnRequest((request, next, cancellationToken) => { - if (request.Method.Equals(HttpMethod.Delete) && request.RequestUri.PathAndQuery.StartsWith("/?id=")) + if (request.Method.Equals(HttpMethod.Delete) && request.RequestUri.PathAndQuery.Contains("&id=")) { deleteCts.Cancel(); return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.Accepted)); diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp.cs b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp.cs new file mode 100644 index 0000000000..35b6c7cc35 --- /dev/null +++ b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp.cs @@ -0,0 +1,49 @@ +// 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. + +namespace Microsoft.AspNetCore.Http.Connections.Client +{ + public partial class HttpConnection : Microsoft.AspNetCore.Connections.ConnectionContext, Microsoft.AspNetCore.Connections.Features.IConnectionInherentKeepAliveFeature + { + public HttpConnection(Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions httpConnectionOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public HttpConnection(System.Uri url) { } + public HttpConnection(System.Uri url, Microsoft.AspNetCore.Http.Connections.HttpTransportType transports) { } + public HttpConnection(System.Uri url, Microsoft.AspNetCore.Http.Connections.HttpTransportType transports, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public override string ConnectionId { get { throw null; } set { } } + public override Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public override System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + bool Microsoft.AspNetCore.Connections.Features.IConnectionInherentKeepAliveFeature.HasInherentKeepAlive { get { throw null; } } + public override System.IO.Pipelines.IDuplexPipe Transport { get { throw null; } set { } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.Task StartAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + } + public partial class HttpConnectionOptions + { + public HttpConnectionOptions() { } + public System.Func> AccessTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get { throw null; } set { } } + public System.TimeSpan CloseTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Net.CookieContainer Cookies { get { throw null; } set { } } + public System.Net.ICredentials Credentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IDictionary Headers { get { throw null; } set { } } + public System.Func HttpMessageHandlerFactory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Net.IWebProxy Proxy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool SkipNegotiation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Http.Connections.HttpTransportType Transports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Uri Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool? UseDefaultCredentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Action WebSocketConfiguration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class NoTransportSupportedException : System.Exception + { + public NoTransportSupportedException(string message) { } + } + public partial class TransportFailedException : System.Exception + { + public TransportFailedException(string transportType, string message, System.Exception innerException = null) { } + public string TransportType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 852681d963..74715c638c 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client // Not configurable on purpose, high enough that if we reach here, it's likely // a buggy server private static readonly int _maxRedirects = 100; + private static readonly int _protocolVersionNumber = 1; private static readonly Task _noAccessToken = Task.FromResult(null); private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120); @@ -41,6 +42,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client private readonly HttpConnectionOptions _httpConnectionOptions; private ITransport _transport; private readonly ITransportFactory _transportFactory; + private string _connectionToken; private string _connectionId; private readonly ConnectionLogScope _logScope; private readonly ILoggerFactory _loggerFactory; @@ -341,7 +343,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client } // This should only need to happen once - var connectUrl = CreateConnectUrl(uri, negotiationResponse.ConnectionId); + var connectUrl = CreateConnectUrl(uri, _connectionToken); // We're going to search for the transfer format as a string because we don't want to parse // all the transfer formats in the negotiation response, and we want to allow transfer formats @@ -382,7 +384,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client if (negotiationResponse == null) { negotiationResponse = await GetNegotiationResponseAsync(uri, cancellationToken); - connectUrl = CreateConnectUrl(uri, negotiationResponse.ConnectionId); + connectUrl = CreateConnectUrl(uri, _connectionToken); } Log.StartingTransport(_logger, transportType, connectUrl); @@ -428,8 +430,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client urlBuilder.Path += "/"; } urlBuilder.Path += "negotiate"; + var uri = Utils.AppendQueryString(urlBuilder.Uri, $"negotiateVersion={_protocolVersionNumber}"); - using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri)) + using (var request = new HttpRequestMessage(HttpMethod.Post, uri)) { // Corefx changed the default version and High Sierra curlhandler tries to upgrade request request.Version = new Version(1, 1); @@ -466,7 +469,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client throw new FormatException("Invalid connection id."); } - return Utils.AppendQueryString(url, "id=" + connectionId); + return Utils.AppendQueryString(url, $"negotiateVersion={_protocolVersionNumber}&id=" + connectionId); } private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat, CancellationToken cancellationToken) @@ -551,14 +554,34 @@ namespace Microsoft.AspNetCore.Http.Connections.Client httpClient.Timeout = HttpClientTimeout; // Start with the user agent header - httpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgentHeader); + httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader); // Apply any headers configured on the HttpConnectionOptions if (_httpConnectionOptions?.Headers != null) { foreach (var header in _httpConnectionOptions.Headers) { - httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + // Check if the key is User-Agent and remove if empty string then replace if it exists. + if (string.Equals(header.Key, Constants.UserAgent, StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrEmpty(header.Value)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + } + else if (httpClient.DefaultRequestHeaders.Contains(header.Key)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } } } @@ -607,7 +630,19 @@ namespace Microsoft.AspNetCore.Http.Connections.Client private async Task GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken) { var negotiationResponse = await NegotiateAsync(uri, _httpClient, _logger, cancellationToken); - _connectionId = negotiationResponse.ConnectionId; + // If the negotiationVersion is greater than zero then we know that the negotiation response contains a + // connectionToken that will be required to conenct. Otherwise we just set the connectionId and the + // connectionToken on the client to the same value. + if (negotiationResponse.Version > 0) + { + _connectionId = negotiationResponse.ConnectionId; + _connectionToken = negotiationResponse.ConnectionToken; + } + else + { + _connectionToken = _connectionId = negotiationResponse.ConnectionId; + } + _logScope.ConnectionId = _connectionId; return negotiationResponse; } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs index 22b41d56f3..c99c7db1a0 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs @@ -3,19 +3,18 @@ using System.Diagnostics; using System.Linq; -using System.Net.Http.Headers; using System.Reflection; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { internal static class Constants { - public static readonly ProductInfoHeaderValue UserAgentHeader; + public const string UserAgent = "User-Agent"; + public static readonly string UserAgentHeader; static Constants() { - var userAgent = "Microsoft.AspNetCore.Http.Connections.Client"; - var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttributes() @@ -23,14 +22,22 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal Debug.Assert(assemblyVersion != null); + var majorVersion = typeof(Constants).Assembly.GetName().Version.Major; + var minorVersion = typeof(Constants).Assembly.GetName().Version.Minor; + var os = RuntimeInformation.OSDescription; + var runtime = ".NET"; + var runtimeVersion = RuntimeInformation.FrameworkDescription; + // assembly version attribute should always be present // but in case it isn't then don't include version in user-agent if (assemblyVersion != null) { - userAgent += "/" + assemblyVersion.InformationalVersion; + UserAgentHeader = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({assemblyVersion.InformationalVersion}; {os}; {runtime}; {runtimeVersion})"; + } + else + { + UserAgentHeader = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({os}; {runtime}; {runtimeVersion})"; } - - UserAgentHeader = ProductInfoHeaderValue.Parse(userAgent); } } } diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index 61b170a40d..8feab7b9b7 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -108,3 +108,27 @@ task generatePOM { } task createPackage(dependsOn: [jar,sourceJar,javadocJar,generatePOM]) + +task generateVersionClass { + inputs.property "version", project.version + outputs.dir "$buildDir/generated" + doFirst { + def versionFile = file("$buildDir/../src/main/java/com/microsoft/signalr/Version.java") + versionFile.parentFile.mkdirs() + versionFile.text = + """ +// 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. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "$project.version"; + } +} +""" + } +} + +compileJava.dependsOn generateVersionClass diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index aa333fb508..57604be1a8 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -57,6 +57,8 @@ public class HubConnection { private Map streamMap = new ConcurrentHashMap<>(); private TransportEnum transportEnum = TransportEnum.ALL; private String connectionId; + private String connectionToken; + private final int negotiateVersion = 1; private final Logger logger = LoggerFactory.getLogger(HubConnection.class); /** @@ -327,6 +329,7 @@ public class HubConnection { handshakeResponseSubject = CompletableSubject.create(); handshakeReceived = false; CompletableSubject tokenCompletable = CompletableSubject.create(); + localHeaders.put(UserAgentHelper.getUserAgentName(), UserAgentHelper.createUserAgentString()); if (headers != null) { this.localHeaders.putAll(headers); } @@ -339,11 +342,12 @@ public class HubConnection { }); stopError = null; + String urlWithQS = Utils.appendQueryString(baseUrl, "negotiateVersion=" + negotiateVersion); Single negotiate = null; if (!skipNegotiate) { - negotiate = tokenCompletable.andThen(Single.defer(() -> startNegotiate(baseUrl, 0))); + negotiate = tokenCompletable.andThen(Single.defer(() -> startNegotiate(urlWithQS, 0))); } else { - negotiate = tokenCompletable.andThen(Single.defer(() -> Single.just(new NegotiateResponse(baseUrl)))); + negotiate = tokenCompletable.andThen(Single.defer(() -> Single.just(new NegotiateResponse(urlWithQS)))); } CompletableSubject start = CompletableSubject.create(); @@ -376,7 +380,6 @@ public class HubConnection { hubConnectionStateLock.lock(); try { hubConnectionState = HubConnectionState.CONNECTED; - this.connectionId = negotiateResponse.getConnectionId(); logger.info("HubConnection started."); resetServerTimeout(); //Don't send pings if we're using long polling. @@ -446,19 +449,21 @@ public class HubConnection { throw new RuntimeException("There were no compatible transports on the server."); } - String finalUrl = url; - if (response.getConnectionId() != null) { - if (url.contains("?")) { - finalUrl = url + "&id=" + response.getConnectionId(); - } else { - finalUrl = url + "?id=" + response.getConnectionId(); - } + if (response.getVersion() > 0) { + this.connectionId = response.getConnectionId(); + this.connectionToken = response.getConnectionToken(); + } else { + this.connectionToken = this.connectionId = response.getConnectionId(); } + + String finalUrl = Utils.appendQueryString(url, "id=" + this.connectionToken); + response.setFinalUrl(finalUrl); return Single.just(response); } - return startNegotiate(response.getRedirectUrl(), negotiateAttempts + 1); + String redirectUrl = Utils.appendQueryString(response.getRedirectUrl(), "negotiateVersion=" + negotiateVersion); + return startNegotiate(redirectUrl, negotiateAttempts + 1); }); } @@ -520,6 +525,7 @@ public class HubConnection { handshakeResponseSubject.onComplete(); redirectAccessTokenProvider = null; connectionId = null; + connectionToken = null; transportEnum = TransportEnum.ALL; this.localHeaders.clear(); this.streamMap.clear(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Negotiate.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Negotiate.java index d63359b90c..d177d32fb9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Negotiate.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Negotiate.java @@ -10,7 +10,7 @@ class Negotiate { // Check if we have a query string. If we do then we ignore it for now. int queryStringIndex = url.indexOf('?'); if (queryStringIndex > 0) { - negotiateUrl = url.substring(0, url.indexOf('?')); + negotiateUrl = url.substring(0, queryStringIndex); } else { negotiateUrl = url; } @@ -24,7 +24,7 @@ class Negotiate { // Add the query string back if it existed. if (queryStringIndex > 0) { - negotiateUrl += url.substring(url.indexOf('?')); + negotiateUrl += url.substring(queryStringIndex); } return negotiateUrl; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/NegotiateResponse.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/NegotiateResponse.java index f115e9601b..bf09b37578 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/NegotiateResponse.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/NegotiateResponse.java @@ -11,11 +11,13 @@ import com.google.gson.stream.JsonReader; class NegotiateResponse { private String connectionId; + private String connectionToken; private Set availableTransports = new HashSet<>(); private String redirectUrl; private String accessToken; private String error; private String finalUrl; + private int version; public NegotiateResponse(JsonReader reader) { try { @@ -30,6 +32,12 @@ class NegotiateResponse { case "ProtocolVersion": this.error = "Detected an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details."; return; + case "negotiateVersion": + this.version = reader.nextInt(); + break; + case "connectionToken": + this.connectionToken = reader.nextString(); + break; case "url": this.redirectUrl = reader.nextString(); break; @@ -106,6 +114,14 @@ class NegotiateResponse { return finalUrl; } + public int getVersion() { + return version; + } + + public String getConnectionToken() { + return connectionToken; + } + public void setFinalUrl(String url) { this.finalUrl = url; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java new file mode 100644 index 0000000000..e54809c700 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java @@ -0,0 +1,54 @@ +// 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. + +package com.microsoft.signalr; + +public class UserAgentHelper { + + private final static String USER_AGENT = "User-Agent"; + + public static String getUserAgentName() { + return USER_AGENT; + } + + public static String createUserAgentString() { + StringBuilder agentBuilder = new StringBuilder("Microsoft SignalR/"); + + // Parsing version numbers + String detailedVersion = Version.getDetailedVersion(); + agentBuilder.append(getVersion(detailedVersion)); + agentBuilder.append(" ("); + agentBuilder.append(detailedVersion); + agentBuilder.append("; "); + + // Getting the OS name + agentBuilder.append(getOS()); + agentBuilder.append("; Java; "); + + // Vendor and Version + agentBuilder.append(getJavaVersion()); + agentBuilder.append("; "); + agentBuilder.append(getJavaVendor()); + agentBuilder.append(")"); + + return agentBuilder.toString(); + } + + static String getVersion(String detailedVersion) { + // Getting the index of the second . so we can return just the major and minor version. + int shortVersionIndex = detailedVersion.indexOf(".", detailedVersion.indexOf(".") + 1); + return detailedVersion.substring(0, shortVersionIndex); + } + + static String getJavaVendor() { + return System.getProperty("java.vendor"); + } + + static String getJavaVersion() { + return System.getProperty("java.version"); + } + + static String getOS() { + return System.getProperty("os.name"); + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java new file mode 100644 index 0000000000..d08c6fb914 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -0,0 +1,14 @@ +// 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. + +package com.microsoft.signalr; + +class Utils { + public static String appendQueryString(String original, String queryStringValue) { + if (original.contains("?")) { + return original + "&" + queryStringValue; + } else { + return original + "?" + queryStringValue; + } + } +} \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java new file mode 100644 index 0000000000..abafa2e53b --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java @@ -0,0 +1,10 @@ +// 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. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "99.99.99-dev"; + } +} diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index f28adf13e7..67aceec18b 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -1116,7 +1116,7 @@ class HubConnectionTest { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); AtomicBoolean done = new AtomicBoolean(); - Single result = hubConnection.invoke(String.class, "fixedMessage", null); + Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); @@ -1714,12 +1714,12 @@ class HubConnectionTest { List sentRequests = client.getSentRequests(); assertEquals(1, sentRequests.size()); - assertEquals("http://example.com/negotiate", sentRequests.get(0).getUrl()); + assertEquals("http://example.com/negotiate?negotiateVersion=1", sentRequests.get(0).getUrl()); } @Test public void negotiateThatRedirectsForeverFailsAfter100Tries() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://example.com\"}"))); HubConnection hubConnection = HubConnectionBuilder @@ -1752,7 +1752,7 @@ class HubConnectionTest { @Test public void connectionIdIsAvailableAfterStart() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); @@ -1775,9 +1775,62 @@ class HubConnectionTest { assertNull(hubConnection.getConnectionId()); } + @Test + public void connectionTokenAppearsInQSConnectionIdIsOnConnectionInstance() { + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> Single.just(new HttpResponse(200, "", + "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"negotiateVersion\": 1," + + "\"connectionToken\":\"connection-token-value\"," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + + MockTransport transport = new MockTransport(true); + HubConnection hubConnection = HubConnectionBuilder + .create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .build(); + + assertEquals(HubConnectionState.DISCONNECTED, hubConnection.getConnectionState()); + assertNull(hubConnection.getConnectionId()); + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", hubConnection.getConnectionId()); + assertEquals("http://example.com?negotiateVersion=1&id=connection-token-value", transport.getUrl()); + hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.DISCONNECTED, hubConnection.getConnectionState()); + assertNull(hubConnection.getConnectionId()); + } + + @Test + public void connectionTokenIsIgnoredIfNegotiateVersionIsNotPresentInNegotiateResponse() { + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> Single.just(new HttpResponse(200, "", + "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"connectionToken\":\"connection-token-value\"," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + + MockTransport transport = new MockTransport(true); + HubConnection hubConnection = HubConnectionBuilder + .create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .build(); + + assertEquals(HubConnectionState.DISCONNECTED, hubConnection.getConnectionState()); + assertNull(hubConnection.getConnectionId()); + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", hubConnection.getConnectionId()); + assertEquals("http://example.com?negotiateVersion=1&id=bVOiRPG8-6YiJ6d7ZcTOVQ", transport.getUrl()); + hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.DISCONNECTED, hubConnection.getConnectionState()); + assertNull(hubConnection.getConnectionId()); + } + @Test public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); @@ -1798,7 +1851,7 @@ class HubConnectionTest { @Test public void afterSuccessfulNegotiateConnectsWithLongPollingTransport() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); @@ -1891,7 +1944,7 @@ class HubConnectionTest { @Test public void receivingServerSentEventsTransportFromNegotiateFails() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}]}"))); @@ -1911,7 +1964,7 @@ class HubConnectionTest { @Test public void negotiateThatReturnsErrorThrowsFromStart() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"error\":\"Test error.\"}"))); MockTransport transport = new MockTransport(true); @@ -1928,7 +1981,7 @@ class HubConnectionTest { @Test public void DetectWhenTryingToConnectToClassicSignalRServer() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"Url\":\"/signalr\"," + "\"ConnectionToken\":\"X97dw3uxW4NPPggQsYVcNcyQcuz4w2\"," + "\"ConnectionId\":\"05265228-1e2c-46c5-82a1-6a5bcc3f0143\"," + @@ -1954,9 +2007,9 @@ class HubConnectionTest { @Test public void negotiateRedirectIsFollowed() { - TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate", + TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) - .on("POST", "http://testexample.com/negotiate", + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); @@ -1978,11 +2031,11 @@ class HubConnectionTest { AtomicReference beforeRedirectToken = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", (req) -> { + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")); }) - .on("POST", "http://testexample.com/negotiate", (req) -> { + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); @@ -2018,7 +2071,7 @@ class HubConnectionTest { public void accessTokenProviderIsUsedForNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" @@ -2043,11 +2096,13 @@ class HubConnectionTest { public void accessTokenProviderIsOverriddenFromRedirectNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) - .on("POST", "http://testexample.com/negotiate", (req) -> { + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"connectionToken\":\"connection-token-value\"," + + "\"negotiateVersion\":1," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); }); MockTransport transport = new MockTransport(true); @@ -2060,7 +2115,7 @@ class HubConnectionTest { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); - assertEquals("http://testexample.com/?id=bVOiRPG8-6YiJ6d7ZcTOVQ", transport.getUrl()); + assertEquals("http://testexample.com/?negotiateVersion=1&id=connection-token-value", transport.getUrl()); hubConnection.stop(); assertEquals("Bearer newToken", token.get()); } @@ -2071,14 +2126,14 @@ class HubConnectionTest { AtomicReference beforeRedirectToken = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", (req) -> { + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")); }) - .on("POST", "http://testexample.com/negotiate", (req) -> { + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); }); MockTransport transport = new MockTransport(true); @@ -2112,7 +2167,7 @@ class HubConnectionTest { AtomicInteger redirectCount = new AtomicInteger(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", (req) -> { + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { if (redirectCount.get() == 0) { redirectCount.incrementAndGet(); redirectToken.set(req.getHeaders().get("Authorization")); @@ -2122,7 +2177,7 @@ class HubConnectionTest { return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"secondRedirectToken\"}")); } }) - .on("POST", "http://testexample.com/negotiate", (req) -> { + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); @@ -2185,11 +2240,82 @@ class HubConnectionTest { } } + @Test + public void userAgentHeaderIsSet() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + + assertTrue(header.get().startsWith("Microsoft SignalR/")); + } + + @Test + public void userAgentHeaderCanBeOverwritten() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "Updated Value") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("Updated Value", header.get()); + } + + @Test + public void userAgentCanBeCleared() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate?negotiateVersion=1", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("", header.get()); + } @Test public void headersAreSetAndSentThroughBuilder() { AtomicReference header = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" @@ -2214,7 +2340,7 @@ class HubConnectionTest { public void headersAreNotClearedWhenConnectionIsRestarted() { AtomicReference header = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" @@ -2244,12 +2370,12 @@ class HubConnectionTest { AtomicReference afterRedirectHeader = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectHeader.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"redirectToken\"}\"}")); }) - .on("POST", "http://testexample.com/negotiate", + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { afterRedirectHeader.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" @@ -2287,7 +2413,7 @@ class HubConnectionTest { public void sameHeaderSetTwiceGetsOverwritten() { AtomicReference header = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" @@ -2332,8 +2458,8 @@ class HubConnectionTest { public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() { MockTransport mockTransport = new MockTransport(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) - .on("POST", "http://testexample.com/negotiate", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); HubConnection hubConnection = HubConnectionBuilder @@ -2355,7 +2481,7 @@ class HubConnectionTest { @Test public void non200FromNegotiateThrowsError() { TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate", + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(500, "Internal server error", ""))); MockTransport transport = new MockTransport(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/NegotiateResponseTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/NegotiateResponseTest.java index 88175d0ac9..1eaa0a00df 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/NegotiateResponseTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/NegotiateResponseTest.java @@ -15,8 +15,9 @@ import com.google.gson.stream.JsonReader; class NegotiateResponseTest { @Test public void VerifyNegotiateResponse() { - String stringNegotiateResponse = "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + - "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}," + + String stringNegotiateResponse = "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"negotiateVersion\": 99, \"connectionToken\":\"connection-token-value\"," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}," + "{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}," + "{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"; NegotiateResponse negotiateResponse = new NegotiateResponse(new JsonReader(new StringReader(stringNegotiateResponse))); @@ -26,6 +27,8 @@ class NegotiateResponseTest { assertNull(negotiateResponse.getAccessToken()); assertNull(negotiateResponse.getRedirectUrl()); assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", negotiateResponse.getConnectionId()); + assertEquals("connection-token-value", negotiateResponse.getConnectionToken()); + assertEquals(99, negotiateResponse.getVersion()); } @Test @@ -56,4 +59,23 @@ class NegotiateResponseTest { NegotiateResponse negotiateResponse = new NegotiateResponse(new JsonReader(new StringReader(stringNegotiateResponse))); assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", negotiateResponse.getConnectionId()); } + + @Test + public void NegotiateResponseWithNegotiateVersion() { + String stringNegotiateResponse = "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"negotiateVersion\": 99}"; + NegotiateResponse negotiateResponse = new NegotiateResponse(new JsonReader(new StringReader(stringNegotiateResponse))); + assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", negotiateResponse.getConnectionId()); + assertEquals(99, negotiateResponse.getVersion()); + } + + @Test + public void NegotiateResponseWithConnectionToken() { + String stringNegotiateResponse = "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"negotiateVersion\": 99, \"connectionToken\":\"connection-token-value\"}"; + NegotiateResponse negotiateResponse = new NegotiateResponse(new JsonReader(new StringReader(stringNegotiateResponse))); + assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", negotiateResponse.getConnectionId()); + assertEquals("connection-token-value", negotiateResponse.getConnectionToken()); + assertEquals(99, negotiateResponse.getVersion()); + } } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java new file mode 100644 index 0000000000..1f92edc07b --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java @@ -0,0 +1,54 @@ +// 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. + +package com.microsoft.signalr; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UserAgentTest { + + private static Stream Versions() { + return Stream.of( + Arguments.of("1.0.0", "1.0"), + Arguments.of("3.1.4-preview9-12345", "3.1"), + Arguments.of("3.1.4-preview9-12345-extrastuff", "3.1"), + Arguments.of("99.99.99-dev", "99.99")); + } + + @ParameterizedTest + @MethodSource("Versions") + public void getVersionFromDetailedVersion(String detailedVersion, String version) { + assertEquals(version, UserAgentHelper.getVersion(detailedVersion)); + } + + @Test + public void verifyJavaVendor() { + assertEquals(System.getProperty("java.vendor"), UserAgentHelper.getJavaVendor()); + } + + @Test + public void verifyJavaVersion() { + assertEquals(System.getProperty("java.version"), UserAgentHelper.getJavaVersion()); + } + + @Test + public void checkUserAgentString() { + String userAgent = UserAgentHelper.createUserAgentString(); + assertNotNull(userAgent); + + String detailedVersion = Version.getDetailedVersion(); + String handMadeUserAgent = "Microsoft SignalR/" + UserAgentHelper.getVersion(detailedVersion) + + " (" + detailedVersion + "; " + UserAgentHelper.getOS() + "; Java; " + + UserAgentHelper.getJavaVersion() + "; " + UserAgentHelper.getJavaVendor() + ")"; + + assertEquals(handMadeUserAgent, userAgent); + } +} diff --git a/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts b/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts index 5720a4e30e..ad6f65db08 100644 --- a/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/scripts/run-tests.ts @@ -245,7 +245,7 @@ function runJest(httpsUrl: string, httpUrl: string) { (async () => { try { - const serverPath = path.resolve(ARTIFACTS_DIR, "bin", "SignalR.Client.FunctionalTestApp", configuration, "netcoreapp3.1", "SignalR.Client.FunctionalTestApp.dll"); + const serverPath = path.resolve(ARTIFACTS_DIR, "bin", "SignalR.Client.FunctionalTestApp", configuration, "netcoreapp5.0", "SignalR.Client.FunctionalTestApp.dll"); debug(`Launching Functional Test Server: ${serverPath}`); let desiredServerUrl = "https://127.0.0.1:0;http://127.0.0.1:0"; diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts index 2bb33c1c11..c66bff8e49 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/Common.ts @@ -1,8 +1,14 @@ // 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. -import { HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; +import { HttpClient, HttpTransportType, IHubProtocol, JsonHubProtocol } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; +import { TestLogger } from "./TestLogger"; + +import { FetchHttpClient } from "@microsoft/signalr/dist/esm/FetchHttpClient"; +import { NodeHttpClient } from "@microsoft/signalr/dist/esm/NodeHttpClient"; +import { Platform } from "@microsoft/signalr/dist/esm/Utils"; +import { XhrHttpClient } from "@microsoft/signalr/dist/esm/XhrHttpClient"; // On slower CI machines, these tests sometimes take longer than 5s jasmine.DEFAULT_TIMEOUT_INTERVAL = 20 * 1000; @@ -97,6 +103,34 @@ export function eachTransportAndProtocol(action: (transport: HttpTransportType, }); } +export function eachTransportAndProtocolAndHttpClient(action: (transport: HttpTransportType, protocol: IHubProtocol, httpClient: HttpClient) => void) { + eachTransportAndProtocol((transport, protocol) => { + getHttpClients().forEach((httpClient) => { + action(transport, protocol, httpClient); + }); + }); +} + export function getGlobalObject(): any { return typeof window !== "undefined" ? window : global; } + +export function getHttpClients(): HttpClient[] { + const httpClients: HttpClient[] = []; + if (typeof XMLHttpRequest !== "undefined") { + httpClients.push(new XhrHttpClient(TestLogger.instance)); + } + if (typeof fetch !== "undefined") { + httpClients.push(new FetchHttpClient(TestLogger.instance)); + } + if (Platform.isNode) { + httpClients.push(new NodeHttpClient(TestLogger.instance)); + } + return httpClients; +} + +export function eachHttpClient(action: (transport: HttpClient) => void) { + return getHttpClients().forEach((t) => { + return action(t); + }); +} diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts index 7760294123..3b559af265 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/ConnectionTests.ts @@ -5,7 +5,7 @@ // tslint:disable:no-floating-promises import { HttpTransportType, IHttpConnectionOptions, TransferFormat } from "@microsoft/signalr"; -import { eachTransport, ECHOENDPOINT_URL } from "./Common"; +import { eachHttpClient, eachTransport, ECHOENDPOINT_URL } from "./Common"; import { TestLogger } from "./TestLogger"; // We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file. @@ -44,109 +44,114 @@ describe("connection", () => { }); eachTransport((transportType) => { - describe(`over ${HttpTransportType[transportType]}`, () => { - it("can send and receive messages", (done) => { - const message = "Hello World!"; - // the url should be resolved relative to the document.location.host - // and the leading '/' should be automatically added to the url - const connection = new HttpConnection(ECHOENDPOINT_URL, { - ...commonOptions, - transport: transportType, - }); + eachHttpClient((httpClient) => { + describe(`over ${HttpTransportType[transportType]} with ${(httpClient.constructor as any).name}`, () => { + it("can send and receive messages", (done) => { + const message = "Hello World!"; + // the url should be resolved relative to the document.location.host + // and the leading '/' should be automatically added to the url + const connection = new HttpConnection(ECHOENDPOINT_URL, { + ...commonOptions, + httpClient, + transport: transportType, + }); - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - connection.onclose = (error: any) => { - expect(error).toBeUndefined(); - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); - }); - }); - - it("does not log content of messages sent or received by default", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - expect(logMessage).not.toContain(message); - } - done(); - }; - - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e) => { - fail(e); - done(); - }); - }); - - it("does log content of messages sent or received when enabled", (done) => { - TestLogger.saveLogsAndReset(); - const message = "Hello World!"; - - // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). - const connection = new HttpConnection(ECHOENDPOINT_URL, { - logMessageContent: true, - logger: TestLogger.instance, - transport: transportType, - }); - - connection.onreceive = (data: any) => { - if (data === message) { - connection.stop(); - } - }; - - // @ts-ignore: We don't use the error parameter intentionally. - connection.onclose = (error) => { - // Search the logs for the message content - let matches = 0; - expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); - // @ts-ignore: We don't use the _ or __ parameters intentionally. - for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { - if (logMessage.indexOf(message) !== -1) { - matches += 1; + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); } - } + }; - // One match for send, one for receive. - expect(matches).toEqual(2); - done(); - }; + connection.onclose = (error: any) => { + expect(error).toBeUndefined(); + done(); + }; - connection.start(TransferFormat.Text).then(() => { - connection.send(message); - }).catch((e: any) => { - fail(e); - done(); + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); + }); + + it("does not log content of messages sent or received by default", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is not set. + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + expect(logMessage).not.toContain(message); + } + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e) => { + fail(e); + done(); + }); + }); + + it("does log content of messages sent or received when enabled", (done) => { + TestLogger.saveLogsAndReset(); + const message = "Hello World!"; + + // DON'T use commonOptions because we want to specifically test the scenario where logMessageContent is set to true (even if commonOptions changes). + const connection = new HttpConnection(ECHOENDPOINT_URL, { + httpClient, + logMessageContent: true, + logger: TestLogger.instance, + transport: transportType, + }); + + connection.onreceive = (data: any) => { + if (data === message) { + connection.stop(); + } + }; + + // @ts-ignore: We don't use the error parameter intentionally. + connection.onclose = (error) => { + // Search the logs for the message content + let matches = 0; + expect(TestLogger.instance.currentLog.messages.length).toBeGreaterThan(0); + // @ts-ignore: We don't use the _ or __ parameters intentionally. + for (const [_, __, logMessage] of TestLogger.instance.currentLog.messages) { + if (logMessage.indexOf(message) !== -1) { + matches += 1; + } + } + + // One match for send, one for receive. + expect(matches).toEqual(2); + done(); + }; + + connection.start(TransferFormat.Text).then(() => { + connection.send(message); + }).catch((e: any) => { + fail(e); + done(); + }); }); }); }); diff --git a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 3d5f434a17..dbfa1a886f 100644 --- a/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/src/SignalR/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -7,7 +7,7 @@ import { AbortError, DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnectionBuilder, IHttpConnectionOptions, JsonHubProtocol, NullLogger } from "@microsoft/signalr"; import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; -import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; +import { eachTransport, eachTransportAndProtocolAndHttpClient, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; import "./LogBannerReporter"; import { TestLogger } from "./TestLogger"; @@ -49,12 +49,12 @@ function getConnectionBuilder(transportType?: HttpTransportType, url?: string, o } describe("hubConnection", () => { - eachTransportAndProtocol((transportType, protocol) => { + eachTransportAndProtocolAndHttpClient((transportType, protocol, httpClient) => { describe("using " + protocol.name + " over " + HttpTransportType[transportType] + " transport", () => { it("can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -81,7 +81,7 @@ describe("hubConnection", () => { it("using https, can invoke server method and receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL) + const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL, { httpClient }) .withHubProtocol(protocol) .build(); @@ -108,7 +108,7 @@ describe("hubConnection", () => { it("can invoke server method non-blocking and not receive result", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -130,7 +130,7 @@ describe("hubConnection", () => { }); it("can invoke server method structural object and receive structural result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -154,7 +154,7 @@ describe("hubConnection", () => { }); it("can stream server method and receive result", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -185,7 +185,7 @@ describe("hubConnection", () => { }); it("can stream server method and cancel stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -219,7 +219,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when invoking", (done) => { const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -241,7 +241,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking streaming method with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -263,7 +263,7 @@ describe("hubConnection", () => { }); it("throws an exception when receiving a streaming result for method called with invoke", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -286,7 +286,7 @@ describe("hubConnection", () => { it("rethrows an exception from the server when streaming", (done) => { const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred."; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -313,7 +313,7 @@ describe("hubConnection", () => { }); it("throws an exception when invoking hub method with stream", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -340,7 +340,7 @@ describe("hubConnection", () => { }); it("can receive server calls", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -370,7 +370,7 @@ describe("hubConnection", () => { }); it("can receive server calls without rebinding handler when restarted", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -425,7 +425,7 @@ describe("hubConnection", () => { }); it("closed with error or start fails if hub cannot be created", async (done) => { - const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable") + const hubConnection = getConnectionBuilder(transportType, ENDPOINT_BASE_URL + "/uncreatable", { httpClient }) .withHubProtocol(protocol) .build(); @@ -446,7 +446,7 @@ describe("hubConnection", () => { }); it("can handle different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -489,7 +489,7 @@ describe("hubConnection", () => { }); it("can receive different types", (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -534,7 +534,7 @@ describe("hubConnection", () => { it("can be restarted", (done) => { const message = "你好,世界!"; - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -577,7 +577,7 @@ describe("hubConnection", () => { }); it("can stream from client to server with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); @@ -594,7 +594,7 @@ describe("hubConnection", () => { }); it("can stream from client to server and close with error with rxjs", async (done) => { - const hubConnection = getConnectionBuilder(transportType) + const hubConnection = getConnectionBuilder(transportType, undefined, { httpClient }) .withHubProtocol(protocol) .build(); diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md index e840374319..00856c5496 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md @@ -12,7 +12,7 @@ yarn add @microsoft/signalr-protocol-msgpack ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. ### Browser diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json index 3d262cc056..d53083aa27 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr-protocol-msgpack", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "MsgPack Protocol support for ASP.NET Core SignalR", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj b/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj index 72978faa2d..1a2b2deac3 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/signalr-protocol-msgpack.npmproj @@ -13,5 +13,9 @@ + + + + diff --git a/src/SignalR/clients/ts/signalr/README.md b/src/SignalR/clients/ts/signalr/README.md index ec8f34b227..3bf0d6534f 100644 --- a/src/SignalR/clients/ts/signalr/README.md +++ b/src/SignalR/clients/ts/signalr/README.md @@ -1,4 +1,4 @@ -JavaScript and TypeScript clients for SignalR for ASP.NET Core +JavaScript and TypeScript clients for SignalR for ASP.NET Core and Azure SignalR Service ## Installation @@ -12,7 +12,9 @@ yarn add @microsoft/signalr ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. + +For documentation on using this client with Azure SignalR Service and Azure Functions, see the [SignalR Service serverless developer guide](https://docs.microsoft.com/azure/azure-signalr/signalr-concept-serverless-development-config). ### Browser diff --git a/src/SignalR/clients/ts/signalr/package.json b/src/SignalR/clients/ts/signalr/package.json index 6b50f24d35..7fcfc9fb29 100644 --- a/src/SignalR/clients/ts/signalr/package.json +++ b/src/SignalR/clients/ts/signalr/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "ASP.NET Core SignalR Client", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/SignalR/clients/ts/signalr/signalr.npmproj b/src/SignalR/clients/ts/signalr/signalr.npmproj index e6a6c1d993..dbd62e31c6 100644 --- a/src/SignalR/clients/ts/signalr/signalr.npmproj +++ b/src/SignalR/clients/ts/signalr/signalr.npmproj @@ -8,5 +8,10 @@ true + + + + + diff --git a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts index fece43020d..8058e5716a 100644 --- a/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { AbortError } from "./Errors"; +import { FetchHttpClient } from "./FetchHttpClient"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger } from "./ILogger"; import { NodeHttpClient } from "./NodeHttpClient"; @@ -15,7 +16,9 @@ export class DefaultHttpClient extends HttpClient { public constructor(logger: ILogger) { super(); - if (typeof XMLHttpRequest !== "undefined") { + if (typeof fetch !== "undefined") { + this.httpClient = new FetchHttpClient(logger); + } else if (typeof XMLHttpRequest !== "undefined") { this.httpClient = new XhrHttpClient(logger); } else { this.httpClient = new NodeHttpClient(logger); diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts new file mode 100644 index 0000000000..48840b42c2 --- /dev/null +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -0,0 +1,121 @@ +// 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. + +import { AbortError, HttpError, TimeoutError } from "./Errors"; +import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; +import { ILogger, LogLevel } from "./ILogger"; + +export class FetchHttpClient extends HttpClient { + private readonly logger: ILogger; + + public constructor(logger: ILogger) { + super(); + this.logger = logger; + } + + /** @inheritDoc */ + public async send(request: HttpRequest): Promise { + // Check that abort was not signaled before calling send + if (request.abortSignal && request.abortSignal.aborted) { + throw new AbortError(); + } + + if (!request.method) { + throw new Error("No method defined."); + } + if (!request.url) { + throw new Error("No url defined."); + } + + const abortController = new AbortController(); + + let error: any; + // Hook our abortSignal into the abort controller + if (request.abortSignal) { + request.abortSignal.onabort = () => { + abortController.abort(); + error = new AbortError(); + }; + } + + // If a timeout has been passed in, setup a timeout to call abort + // Type needs to be any to fit window.setTimeout and NodeJS.setTimeout + let timeoutId: any = null; + if (request.timeout) { + const msTimeout = request.timeout!; + timeoutId = setTimeout(() => { + abortController.abort(); + this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); + error = new TimeoutError(); + }, msTimeout); + } + + let response: Response; + try { + response = await fetch(request.url!, { + body: request.content!, + cache: "no-cache", + credentials: "include", + headers: { + "Content-Type": "text/plain;charset=UTF-8", + "X-Requested-With": "XMLHttpRequest", + ...request.headers, + }, + method: request.method!, + mode: "cors", + redirect: "manual", + signal: abortController.signal, + }); + } catch (e) { + if (error) { + throw error; + } + this.logger.log( + LogLevel.Warning, + `Error from HTTP request. ${e}.`, + ); + throw e; + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (request.abortSignal) { + request.abortSignal.onabort = null; + } + } + + if (!response.ok) { + throw new HttpError(response.statusText, response.status); + } + + const content = deserializeContent(response, request.responseType); + const payload = await content; + + return new HttpResponse( + response.status, + response.statusText, + payload, + ); + } +} + +function deserializeContent(response: Response, responseType?: XMLHttpRequestResponseType): Promise { + let content; + switch (responseType) { + case "arraybuffer": + content = response.arrayBuffer(); + break; + case "text": + content = response.text(); + break; + case "blob": + case "document": + case "json": + throw new Error(`${responseType} is not supported.`); + default: + content = response.text(); + break; + } + + return content; +} diff --git a/src/SignalR/clients/ts/signalr/src/HttpClient.ts b/src/SignalR/clients/ts/signalr/src/HttpClient.ts index 9685feca5e..c50289bbf1 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpClient.ts @@ -57,6 +57,14 @@ export class HttpResponse { * @param {ArrayBuffer} content The content of the response. */ constructor(statusCode: number, statusText: string, content: ArrayBuffer); + + /** Constructs a new instance of {@link @microsoft/signalr.HttpResponse} with the specified status code, message and binary content. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + * @param {string | ArrayBuffer} content The content of the response. + */ + constructor(statusCode: number, statusText: string, content: string | ArrayBuffer); constructor( public readonly statusCode: number, public readonly statusText?: string, diff --git a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts index 98a23a3b3d..fa5d7432b9 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -70,14 +70,14 @@ export class HubConnectionBuilder { /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {string} logLevel A string representing a LogLevel setting a minimum level of messages to log. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. */ public configureLogging(logLevel: string): HubConnectionBuilder; /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {LogLevel | string | ILogger} logging A {@link @microsoft/signalr.LogLevel}, a string representing a LogLevel, or an object implementing the {@link @microsoft/signalr.ILogger} interface. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. * @returns The {@link @microsoft/signalr.HubConnectionBuilder} instance, for chaining. */ public configureLogging(logging: LogLevel | string | ILogger): HubConnectionBuilder; diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs index 27d206da33..f557b74f58 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs @@ -34,7 +34,9 @@ namespace Microsoft.AspNetCore.Http.Connections public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs index 27d206da33..f557b74f58 100644 --- a/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs +++ b/src/SignalR/common/Http.Connections.Common/ref/Microsoft.AspNetCore.Http.Connections.Common.netstandard2.0.cs @@ -34,7 +34,9 @@ namespace Microsoft.AspNetCore.Http.Connections public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IList AvailableTransports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ConnectionToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Error { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Url { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int Version { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } diff --git a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj index 521467f849..c40fa68e6d 100644 --- a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj +++ b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs index a23a9d6c0b..ae69b56cdd 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs @@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Http.Connections { private const string ConnectionIdPropertyName = "connectionId"; private static JsonEncodedText ConnectionIdPropertyNameBytes = JsonEncodedText.Encode(ConnectionIdPropertyName); + private const string ConnectionTokenPropertyName = "connectionToken"; + private static JsonEncodedText ConnectionTokenPropertyNameBytes = JsonEncodedText.Encode(ConnectionTokenPropertyName); private const string UrlPropertyName = "url"; private static JsonEncodedText UrlPropertyNameBytes = JsonEncodedText.Encode(UrlPropertyName); private const string AccessTokenPropertyName = "accessToken"; @@ -27,6 +29,8 @@ namespace Microsoft.AspNetCore.Http.Connections private static JsonEncodedText TransferFormatsPropertyNameBytes = JsonEncodedText.Encode(TransferFormatsPropertyName); private const string ErrorPropertyName = "error"; private static JsonEncodedText ErrorPropertyNameBytes = JsonEncodedText.Encode(ErrorPropertyName); + private const string NegotiateVersionPropertyName = "negotiateVersion"; + private static JsonEncodedText NegotiateVersionPropertyNameBytes = JsonEncodedText.Encode(NegotiateVersionPropertyName); // Use C#7.3's ReadOnlySpan optimization for static data https://vcsjones.com/2019/02/01/csharp-readonly-span-bytes-static/ // Used to detect ASP.NET SignalR Server connection attempt @@ -41,6 +45,19 @@ namespace Microsoft.AspNetCore.Http.Connections var writer = reusableWriter.GetJsonWriter(); writer.WriteStartObject(); + // If we already have an error its due to a protocol version incompatibility. + // We can just write the error and complete the JSON object and return. + if (!string.IsNullOrEmpty(response.Error)) + { + writer.WriteString(ErrorPropertyNameBytes, response.Error); + writer.WriteEndObject(); + writer.Flush(); + Debug.Assert(writer.CurrentDepth == 0); + return; + } + + writer.WriteNumber(NegotiateVersionPropertyNameBytes, response.Version); + if (!string.IsNullOrEmpty(response.Url)) { writer.WriteString(UrlPropertyNameBytes, response.Url); @@ -56,6 +73,11 @@ namespace Microsoft.AspNetCore.Http.Connections writer.WriteString(ConnectionIdPropertyNameBytes, response.ConnectionId); } + if (response.Version > 0 && !string.IsNullOrEmpty(response.ConnectionToken)) + { + writer.WriteString(ConnectionTokenPropertyNameBytes, response.ConnectionToken); + } + writer.WriteStartArray(AvailableTransportsPropertyNameBytes); if (response.AvailableTransports != null) @@ -112,10 +134,12 @@ namespace Microsoft.AspNetCore.Http.Connections reader.EnsureObjectStart(); string connectionId = null; + string connectionToken = null; string url = null; string accessToken = null; List availableTransports = null; string error = null; + int version = 0; var completed = false; while (!completed && reader.CheckRead()) @@ -135,6 +159,14 @@ namespace Microsoft.AspNetCore.Http.Connections { connectionId = reader.ReadAsString(ConnectionIdPropertyName); } + else if (reader.ValueTextEquals(ConnectionTokenPropertyNameBytes.EncodedUtf8Bytes)) + { + connectionToken = reader.ReadAsString(ConnectionTokenPropertyName); + } + else if (reader.ValueTextEquals(NegotiateVersionPropertyNameBytes.EncodedUtf8Bytes)) + { + version = reader.ReadAsInt32(NegotiateVersionPropertyName).GetValueOrDefault(); + } else if (reader.ValueTextEquals(AvailableTransportsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); @@ -182,6 +214,14 @@ namespace Microsoft.AspNetCore.Http.Connections throw new InvalidDataException($"Missing required property '{ConnectionIdPropertyName}'."); } + if (version > 0) + { + if (connectionToken == null) + { + throw new InvalidDataException($"Missing required property '{ConnectionTokenPropertyName}'."); + } + } + if (availableTransports == null) { throw new InvalidDataException($"Missing required property '{AvailableTransportsPropertyName}'."); @@ -191,10 +231,12 @@ namespace Microsoft.AspNetCore.Http.Connections return new NegotiationResponse { ConnectionId = connectionId, + ConnectionToken = connectionToken, Url = url, AccessToken = accessToken, AvailableTransports = availableTransports, Error = error, + Version = version }; } catch (Exception ex) @@ -222,13 +264,11 @@ namespace Microsoft.AspNetCore.Http.Connections switch (reader.TokenType) { case JsonTokenType.PropertyName: - var memberName = reader.ValueSpan; - - if (memberName.SequenceEqual(TransportPropertyNameBytes.EncodedUtf8Bytes)) + if (reader.ValueTextEquals(TransportPropertyNameBytes.EncodedUtf8Bytes)) { availableTransport.Transport = reader.ReadAsString(TransportPropertyName); } - else if (memberName.SequenceEqual(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) + else if (reader.ValueTextEquals(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); reader.EnsureArrayStart(); diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs index a01d2e637c..69810a5a71 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore.Http.Connections public string Url { get; set; } public string AccessToken { get; set; } public string ConnectionId { get; set; } + public string ConnectionToken { get; set; } + public int Version { get; set; } public IList AvailableTransports { get; set; } public string Error { get; set; } } diff --git a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs index 7810a4985d..5ee369727c 100644 --- a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs +++ b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp.cs @@ -53,6 +53,7 @@ namespace Microsoft.AspNetCore.Http.Connections public long ApplicationMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Collections.Generic.IList AuthorizationData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Http.Connections.LongPollingOptions LongPolling { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public int MinimumProtocolVersion { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public long TransportMaxBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.Connections.HttpTransportType Transports { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.Connections.WebSocketOptions WebSockets { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } diff --git a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs index eff4ae76e4..e1f97d7183 100644 --- a/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs +++ b/src/SignalR/common/Http.Connections/src/HttpConnectionDispatcherOptions.cs @@ -57,5 +57,11 @@ namespace Microsoft.AspNetCore.Http.Connections /// Gets or sets the maximum buffer size of the application writer. /// public long ApplicationMaxBufferSize { get; set; } + + /// + /// Gets or sets the minimum protocol verison supported by the server. + /// The default value is 0, the lowest possible protocol version. + /// + public int MinimumProtocolVersion { get; set; } = 0; } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs index 6e21d7a665..6d3fe467e9 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs @@ -48,11 +48,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal /// Creates the DefaultConnectionContext without Pipes to avoid upfront allocations. /// The caller is expected to set the and pipes manually. /// - /// + /// + /// /// - public HttpConnectionContext(string id, ILogger logger) + public HttpConnectionContext(string connectionId, string connectionToken, ILogger logger) { - ConnectionId = id; + ConnectionId = connectionId; + ConnectionToken = connectionToken; LastSeenUtc = DateTime.UtcNow; // The default behavior is that both formats are supported. @@ -74,8 +76,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal Features.Set(this); } - public HttpConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application, ILogger logger = null) - : this(id, logger) + internal HttpConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application, ILogger logger = null) + : this(id, null, logger) { Transport = transport; Application = application; @@ -113,6 +115,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal public override string ConnectionId { get; set; } + internal string ConnectionToken { get; set; } + public override IFeatureCollection Features { get; } public ClaimsPrincipal User { get; set; } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs index af91f08af2..80f3d32800 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs @@ -52,6 +52,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private static readonly Action _failedToReadHttpRequestBody = LoggerMessage.Define(LogLevel.Debug, new EventId(14, "FailedToReadHttpRequestBody"), "Connection {TransportConnectionId} failed to read the HTTP request body."); + private static readonly Action _negotiateProtocolVersionMismatch = + LoggerMessage.Define(LogLevel.Debug, new EventId(15, "NegotiateProtocolVersionMismatch"), "The client requested version '{clientProtocolVersion}', but the server does not support this version."); + + private static readonly Action _invalidNegotiateProtocolVersion = + LoggerMessage.Define(LogLevel.Debug, new EventId(16, "InvalidNegotiateProtocolVersion"), "The client requested an invalid protocol version '{queryStringVersionValue}'"); + public static void ConnectionDisposed(ILogger logger, string connectionId) { _connectionDisposed(logger, connectionId, null); @@ -121,6 +127,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal { _failedToReadHttpRequestBody(logger, connectionId, ex); } + + public static void NegotiateProtocolVersionMismatch(ILogger logger, int clientProtocolVersion) + { + _negotiateProtocolVersionMismatch(logger, clientProtocolVersion, null); + } + + public static void InvalidNegotiateProtocolVersion(ILogger logger, string requestedProtocolVersion) + { + _invalidNegotiateProtocolVersion(logger, requestedProtocolVersion, null); + } } } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index bf82562a7b..7bd4acc682 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private readonly HttpConnectionManager _manager; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; + private static readonly int _protocolVersion = 1; public HttpConnectionDispatcher(HttpConnectionManager manager, ILoggerFactory loggerFactory) { @@ -58,7 +59,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal // Create the log scope and attempt to pass the Connection ID to it so as many logs as possible contain // the Connection ID metadata. If this is the negotiate request then the Connection ID for the scope will // be set a little later. - var logScope = new ConnectionLogScope(GetConnectionId(context)); + + HttpConnectionContext connectionContext = null; + var connectionToken = GetConnectionToken(context); + if (connectionToken != null) + { + _manager.TryGetConnection(GetConnectionToken(context), out connectionContext); + } + + var logScope = new ConnectionLogScope(connectionContext?.ConnectionId); using (_logger.BeginScope(logScope)) { if (HttpMethods.IsPost(context.Request.Method)) @@ -278,13 +287,29 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private async Task ProcessNegotiate(HttpContext context, HttpConnectionDispatcherOptions options, ConnectionLogScope logScope) { context.Response.ContentType = "application/json"; + string error = null; + int clientProtocolVersion = 0; + if (context.Request.Query.TryGetValue("NegotiateVersion", out var queryStringVersion)) + { + // Set the negotiate response to the protocol we use. + var queryStringVersionValue = queryStringVersion.ToString(); + if (!int.TryParse(queryStringVersionValue, out clientProtocolVersion)) + { + error = $"The client requested an invalid protocol version '{queryStringVersionValue}'"; + Log.InvalidNegotiateProtocolVersion(_logger, queryStringVersionValue); + } + } // Establish the connection - var connection = CreateConnection(options); + HttpConnectionContext connection = null; + if (error == null) + { + connection = CreateConnection(options, clientProtocolVersion); + } // Set the Connection ID on the logging scope so that logs from now on will have the // Connection ID metadata set. - logScope.ConnectionId = connection.ConnectionId; + logScope.ConnectionId = connection?.ConnectionId; // Don't use thread static instance here because writer is used with async var writer = new MemoryBufferWriter(); @@ -292,7 +317,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal try { // Get the bytes for the connection id - WriteNegotiatePayload(writer, connection.ConnectionId, context, options); + WriteNegotiatePayload(writer, connection?.ConnectionId, connection?.ConnectionToken, context, options, clientProtocolVersion, error); Log.NegotiationRequest(_logger); @@ -306,10 +331,46 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal } } - private static void WriteNegotiatePayload(IBufferWriter writer, string connectionId, HttpContext context, HttpConnectionDispatcherOptions options) + private void WriteNegotiatePayload(IBufferWriter writer, string connectionId, string connectionToken, HttpContext context, HttpConnectionDispatcherOptions options, + int clientProtocolVersion, string error) { var response = new NegotiationResponse(); + + if (!string.IsNullOrEmpty(error)) + { + response.Error = error; + NegotiateProtocol.WriteResponse(response, writer); + return; + } + + if (clientProtocolVersion > 0) + { + if (clientProtocolVersion < options.MinimumProtocolVersion) + { + response.Error = $"The client requested version '{clientProtocolVersion}', but the server does not support this version."; + Log.NegotiateProtocolVersionMismatch(_logger, clientProtocolVersion); + NegotiateProtocol.WriteResponse(response, writer); + return; + } + else if (clientProtocolVersion > _protocolVersion) + { + response.Version = _protocolVersion; + } + else + { + response.Version = clientProtocolVersion; + } + } + else if (options.MinimumProtocolVersion > 0) + { + // NegotiateVersion wasn't parsed meaning the client requests version 0. + response.Error = $"The client requested version '0', but the server does not support this version."; + NegotiateProtocol.WriteResponse(response, writer); + return; + } + response.ConnectionId = connectionId; + response.ConnectionToken = connectionToken; response.AvailableTransports = new List(); if ((options.Transports & HttpTransportType.WebSockets) != 0 && ServerHasWebSockets(context.Features)) @@ -335,7 +396,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal return features.Get() != null; } - private static string GetConnectionId(HttpContext context) => context.Request.Query["id"]; + private static string GetConnectionToken(HttpContext context) => context.Request.Query["id"]; private async Task ProcessSend(HttpContext context, HttpConnectionDispatcherOptions options) { @@ -608,9 +669,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private async Task GetConnectionAsync(HttpContext context) { - var connectionId = GetConnectionId(context); + var connectionToken = GetConnectionToken(context); - if (StringValues.IsNullOrEmpty(connectionId)) + if (StringValues.IsNullOrEmpty(connectionToken)) { // There's no connection ID: bad request context.Response.StatusCode = StatusCodes.Status400BadRequest; @@ -619,7 +680,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal return null; } - if (!_manager.TryGetConnection(connectionId, out var connection)) + if (!_manager.TryGetConnection(connectionToken, out var connection)) { // No connection with that ID: Not Found context.Response.StatusCode = StatusCodes.Status404NotFound; @@ -634,15 +695,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal // This is only used for WebSockets connections, which can connect directly without negotiating private async Task GetOrCreateConnectionAsync(HttpContext context, HttpConnectionDispatcherOptions options) { - var connectionId = GetConnectionId(context); + var connectionToken = GetConnectionToken(context); HttpConnectionContext connection; // There's no connection id so this is a brand new connection - if (StringValues.IsNullOrEmpty(connectionId)) + if (StringValues.IsNullOrEmpty(connectionToken)) { connection = CreateConnection(options); } - else if (!_manager.TryGetConnection(connectionId, out connection)) + else if (!_manager.TryGetConnection(connectionToken, out connection)) { // No connection with that ID: Not Found context.Response.StatusCode = StatusCodes.Status404NotFound; @@ -653,12 +714,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal return connection; } - private HttpConnectionContext CreateConnection(HttpConnectionDispatcherOptions options) + private HttpConnectionContext CreateConnection(HttpConnectionDispatcherOptions options, int clientProtocolVersion = 0) { var transportPipeOptions = new PipeOptions(pauseWriterThreshold: options.TransportMaxBufferSize, resumeWriterThreshold: options.TransportMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); var appPipeOptions = new PipeOptions(pauseWriterThreshold: options.ApplicationMaxBufferSize, resumeWriterThreshold: options.ApplicationMaxBufferSize / 2, readerScheduler: PipeScheduler.ThreadPool, useSynchronizationContext: false); - - return _manager.CreateConnection(transportPipeOptions, appPipeOptions); + return _manager.CreateConnection(transportPipeOptions, appPipeOptions, clientProtocolVersion); } private class EmptyServiceProvider : IServiceProvider diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs index dda35866a4..4a97681fc0 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs @@ -78,18 +78,28 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal /// Creates a connection without Pipes setup to allow saving allocations until Pipes are needed. /// /// - internal HttpConnectionContext CreateConnection(PipeOptions transportPipeOptions, PipeOptions appPipeOptions) + internal HttpConnectionContext CreateConnection(PipeOptions transportPipeOptions, PipeOptions appPipeOptions, int negotiateVersion = 0) { + string connectionToken; var id = MakeNewConnectionId(); + if (negotiateVersion > 0) + { + connectionToken = MakeNewConnectionId(); + } + else + { + connectionToken = id; + } Log.CreatedNewConnection(_logger, id); var connectionTimer = HttpConnectionsEventSource.Log.ConnectionStart(id); - var connection = new HttpConnectionContext(id, _connectionLogger); + var connection = new HttpConnectionContext(id, connectionToken, _connectionLogger); var pair = DuplexPipe.CreateConnectionPair(transportPipeOptions, appPipeOptions); connection.Transport = pair.Application; connection.Application = pair.Transport; - _connections.TryAdd(id, (connection, connectionTimer)); + _connections.TryAdd(connectionToken, (connection, connectionTimer)); + return connection; } @@ -205,7 +215,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal { // Remove it from the list after disposal so that's it's easy to see // connections that might be in a hung state via the connections list - RemoveConnection(connection.ConnectionId); + RemoveConnection(connection.ConnectionToken); } } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsServerTransport.cs b/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsServerTransport.cs index ef54b75288..d815b25e54 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsServerTransport.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/Transports/WebSocketsServerTransport.cs @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports var memory = _application.Output.GetMemory(); var receiveResult = await socket.ReceiveAsync(memory, token); - // Need to check again for netcoreapp3.1 because a close can happen between a 0-byte read and the actual read + // Need to check again for netcoreapp5.0 because a close can happen between a 0-byte read and the actual read if (receiveResult.MessageType == WebSocketMessageType.Close) { return; diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 7b164b8929..6e28f47cc6 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests public class HttpConnectionDispatcherTests : VerifiableLoggedTest { [Fact] - public async Task NegotiateReservesConnectionIdAndReturnsIt() + public async Task NegotiateVersionZeroReservesConnectionIdAndReturnsIt() { using (StartVerifiableLog()) { @@ -54,8 +54,35 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions()); var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); var connectionId = negotiateResponse.Value("connectionId"); - Assert.True(manager.TryGetConnection(connectionId, out var connectionContext)); + var connectionToken = negotiateResponse.Value("connectionToken"); + Assert.Null(connectionToken); + Assert.NotNull(connectionId); + } + } + + [Fact] + public async Task NegotiateReservesConnectionTokenAndConnectionIdAndReturnsIt() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + var ms = new MemoryStream(); + context.Request.Path = "/foo"; + context.Request.Method = "POST"; + context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); + await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions()); + var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); + var connectionId = negotiateResponse.Value("connectionId"); + var connectionToken = negotiateResponse.Value("connectionToken"); + Assert.True(manager.TryGetConnection(connectionToken, out var connectionContext)); Assert.Equal(connectionId, connectionContext.ConnectionId); + Assert.NotEqual(connectionId, connectionToken); } } @@ -74,12 +101,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4 }; await dispatcher.ExecuteNegotiateAsync(context, options); var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); - var connectionId = negotiateResponse.Value("connectionId"); - context.Request.QueryString = context.Request.QueryString.Add("id", connectionId); - Assert.True(manager.TryGetConnection(connectionId, out var connection)); + var connectionToken = negotiateResponse.Value("connectionToken"); + context.Request.QueryString = context.Request.QueryString.Add("id", connectionToken); + Assert.True(manager.TryGetConnection(connectionToken, out var connection)); // Fake actual connection after negotiate to populate the pipes on the connection await dispatcher.ExecuteAsync(context, options, c => Task.CompletedTask); @@ -95,6 +123,62 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } + [Fact] + public async Task InvalidNegotiateProtocolVersionThrows() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + var ms = new MemoryStream(); + context.Request.Path = "/foo"; + context.Request.Method = "POST"; + context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=Invalid"); + var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4 }; + await dispatcher.ExecuteNegotiateAsync(context, options); + var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); + + var error = negotiateResponse.Value("error"); + Assert.Equal("The client requested an invalid protocol version 'Invalid'", error); + + var connectionId = negotiateResponse.Value("connectionId"); + Assert.Null(connectionId); + } + } + + [Fact] + public async Task NoNegotiateVersionInQueryStringThrowsWhenMinProtocolVersionIsSet() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var context = new DefaultHttpContext(); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddOptions(); + var ms = new MemoryStream(); + context.Request.Path = "/foo"; + context.Request.Method = "POST"; + context.Response.Body = ms; + context.Request.QueryString = new QueryString(""); + var options = new HttpConnectionDispatcherOptions { TransportMaxBufferSize = 4, ApplicationMaxBufferSize = 4, MinimumProtocolVersion = 1 }; + await dispatcher.ExecuteNegotiateAsync(context, options); + var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); + + var error = negotiateResponse.Value("error"); + Assert.Equal("The client requested version '0', but the server does not support this version.", error); + + var connectionId = negotiateResponse.Value("connectionId"); + Assert.Null(connectionId); + } + } + [Theory] [InlineData(HttpTransportType.LongPolling)] [InlineData(HttpTransportType.ServerSentEvents)] @@ -125,7 +209,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -166,6 +251,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions { Transports = transports }); var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); @@ -204,6 +290,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Method = "GET"; var values = new Dictionary(); values["id"] = "unknown"; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; SetTransport(context, transportType); @@ -240,6 +327,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Method = "POST"; var values = new Dictionary(); values["id"] = "unknown"; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -276,7 +364,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -315,6 +404,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Method = "POST"; var values = new Dictionary(); values["id"] = connection.ConnectionId; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -354,7 +444,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "GET"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -415,7 +506,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "GET"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -481,7 +573,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -544,6 +637,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Method = "POST"; var values = new Dictionary(); values["id"] = connection.ConnectionId; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -613,7 +707,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "GET"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; values["another"] = "value"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -661,8 +756,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var connectionHttpContext = connection.GetHttpContext(); Assert.NotNull(connectionHttpContext); - Assert.Equal(2, connectionHttpContext.Request.Query.Count); - Assert.Equal(connection.ConnectionId, connectionHttpContext.Request.Query["id"]); + Assert.Equal(3, connectionHttpContext.Request.Query.Count); Assert.Equal("value", connectionHttpContext.Request.Query["another"]); Assert.Equal(3, connectionHttpContext.Request.Headers.Count); @@ -706,6 +800,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests services.AddSingleton(); context.Request.Path = "/foo"; context.Request.Method = "GET"; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); SetTransport(context, transportType); @@ -748,7 +843,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -775,6 +871,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests services.AddSingleton(); context.Request.Path = "/foo"; context.Request.Method = "POST"; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); @@ -846,6 +943,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); var context = MakeRequest("/foo", connection); + SetTransport(context, HttpTransportType.ServerSentEvents); var services = new ServiceCollection(); @@ -857,7 +955,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); - var exists = manager.TryGetConnection(connection.ConnectionId, out _); + var exists = manager.TryGetConnection(connection.ConnectionToken, out _); Assert.False(exists); } } @@ -1221,7 +1319,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await task; Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); - var exists = manager.TryGetConnection(connection.ConnectionId, out _); + var exists = manager.TryGetConnection(connection.ConnectionToken, out _); Assert.False(exists); } } @@ -1262,7 +1360,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await task; Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode); - var exists = manager.TryGetConnection(connection.ConnectionId, out _); + var exists = manager.TryGetConnection(connection.ConnectionToken, out _); Assert.False(exists); } } @@ -1364,10 +1462,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Method = "GET"; context.RequestServices = sp; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; - var builder = new ConnectionBuilder(sp); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1452,7 +1550,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests // Issue the delete request var deleteContext = new DefaultHttpContext(); deleteContext.Request.Path = "/foo"; - deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionId}"); + deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionToken}"); deleteContext.Request.Method = "DELETE"; var ms = new MemoryStream(); deleteContext.Response.Body = ms; @@ -1495,7 +1593,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests // Issue the delete request and make sure the poll completes var deleteContext = new DefaultHttpContext(); deleteContext.Request.Path = "/foo"; - deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionId}"); + deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionToken}"); deleteContext.Request.Method = "DELETE"; Assert.False(pollTask.IsCompleted); @@ -1513,7 +1611,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.Equal("text/plain", deleteContext.Response.ContentType); // Verify the connection was removed from the manager - Assert.False(manager.TryGetConnection(connection.ConnectionId, out _)); + Assert.False(manager.TryGetConnection(connection.ConnectionToken, out _)); } } @@ -1543,7 +1641,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests // Issue the delete request and make sure the poll completes var deleteContext = new DefaultHttpContext(); deleteContext.Request.Path = "/foo"; - deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionId}"); + deleteContext.Request.QueryString = new QueryString($"?id={connection.ConnectionToken}"); deleteContext.Request.Method = "DELETE"; await dispatcher.ExecuteAsync(deleteContext, options, app).OrTimeout(); @@ -1561,7 +1659,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await connection.DisposeAndRemoveTask.OrTimeout(); // Verify the connection was removed from the manager - Assert.False(manager.TryGetConnection(connection.ConnectionId, out _)); + Assert.False(manager.TryGetConnection(connection.ConnectionToken, out _)); } } @@ -1581,6 +1679,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; context.Response.Body = ms; + context.Request.QueryString = new QueryString("?negotiateVersion=1"); await dispatcher.ExecuteNegotiateAsync(context, new HttpConnectionDispatcherOptions { Transports = HttpTransportType.WebSockets }); var negotiateResponse = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(ms.ToArray())); @@ -1637,7 +1736,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; var buffer = Encoding.UTF8.GetBytes("Hello, world"); @@ -1693,7 +1793,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; var buffer = Encoding.UTF8.GetBytes("Hello, world"); @@ -1746,7 +1847,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "POST"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; var buffer = Encoding.UTF8.GetBytes("Hello, world"); @@ -1808,7 +1910,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await pollTask.OrTimeout(); Assert.Equal(StatusCodes.Status500InternalServerError, pollContext.Response.StatusCode); - Assert.False(manager.TryGetConnection(connection.ConnectionId, out var _)); + Assert.False(manager.TryGetConnection(connection.ConnectionToken, out var _)); } } @@ -1831,7 +1933,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Request.Path = "/foo"; context.Request.Method = "GET"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; @@ -1853,14 +1956,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } - private static DefaultHttpContext MakeRequest(string path, ConnectionContext connection, string format = null) + private static DefaultHttpContext MakeRequest(string path, HttpConnectionContext connection, string format = null) { var context = new DefaultHttpContext(); context.Features.Set(new ResponseFeature()); context.Request.Path = path; context.Request.Method = "GET"; var values = new Dictionary(); - values["id"] = connection.ConnectionId; + values["id"] = connection.ConnectionToken; + values["negotiateVersion"] = "1"; if (format != null) { values["format"] = format; diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs index 5c30a490f7..ade605b08a 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.NotNull(connection.ConnectionId); - Assert.True(connectionManager.TryGetConnection(connection.ConnectionId, out var newConnection)); + Assert.True(connectionManager.TryGetConnection(connection.ConnectionToken, out var newConnection)); Assert.Same(newConnection, connection); } } @@ -143,13 +143,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests { var connectionManager = CreateConnectionManager(LoggerFactory); var connection = connectionManager.CreateConnection(PipeOptions.Default, PipeOptions.Default); - var transport = connection.Transport; Assert.NotNull(connection.ConnectionId); + Assert.NotNull(connection.ConnectionToken); Assert.NotNull(transport); - Assert.True(connectionManager.TryGetConnection(connection.ConnectionId, out var newConnection)); + Assert.True(connectionManager.TryGetConnection(connection.ConnectionToken, out var newConnection)); Assert.Same(newConnection, connection); Assert.Same(transport, newConnection.Transport); } @@ -168,12 +168,55 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.NotNull(connection.ConnectionId); Assert.NotNull(transport); - Assert.True(connectionManager.TryGetConnection(connection.ConnectionId, out var newConnection)); + Assert.True(connectionManager.TryGetConnection(connection.ConnectionToken, out var newConnection)); Assert.Same(newConnection, connection); Assert.Same(transport, newConnection.Transport); - connectionManager.RemoveConnection(connection.ConnectionId); - Assert.False(connectionManager.TryGetConnection(connection.ConnectionId, out newConnection)); + connectionManager.RemoveConnection(connection.ConnectionToken); + Assert.False(connectionManager.TryGetConnection(connection.ConnectionToken, out newConnection)); + } + } + + [Fact] + public void ConnectionIdAndConnectionTokenAreTheSameForNegotiateVersionZero() + { + using (StartVerifiableLog()) + { + var connectionManager = CreateConnectionManager(LoggerFactory); + var connection = connectionManager.CreateConnection(PipeOptions.Default, PipeOptions.Default, negotiateVersion: 0); + + var transport = connection.Transport; + + Assert.NotNull(connection.ConnectionId); + Assert.NotNull(transport); + + Assert.True(connectionManager.TryGetConnection(connection.ConnectionToken, out var newConnection)); + Assert.Same(newConnection, connection); + Assert.Same(transport, newConnection.Transport); + Assert.Equal(connection.ConnectionId, connection.ConnectionToken); + + } + } + + [Fact] + public void ConnectionIdAndConnectionTokenAreDifferentForNegotiateVersionOne() + { + using (StartVerifiableLog()) + { + var connectionManager = CreateConnectionManager(LoggerFactory); + var connection = connectionManager.CreateConnection(PipeOptions.Default, PipeOptions.Default, negotiateVersion: 1); + + var transport = connection.Transport; + + Assert.NotNull(connection.ConnectionId); + Assert.NotNull(transport); + + Assert.True(connectionManager.TryGetConnection(connection.ConnectionToken, out var newConnection)); + Assert.False(connectionManager.TryGetConnection(connection.ConnectionId, out var _)); + Assert.Same(newConnection, connection); + Assert.Same(transport, newConnection.Transport); + Assert.NotEqual(connection.ConnectionId, connection.ConnectionToken); + } } diff --git a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs index e92d3c3b42..00d803ffdd 100644 --- a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs +++ b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs @@ -13,12 +13,20 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests public class NegotiateProtocolTests { [Theory] - [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null)] - [InlineData("{\"connectionId\":\"\",\"availableTransports\":[]}", "", new string[0], null, null)] - [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null)] - [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token")] - [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null)] - public void ParsingNegotiateResponseMessageSuccessForValid(string json, string connectionId, string[] availableTransports, string url, string accessToken) + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 0, null)] + [InlineData("{\"connectionId\":\"\",\"availableTransports\":[]}", "", new string[0], null, null, 0, null)] + [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null, 0, null)] + [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token", 0, null)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0, null)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 0, null)] + [InlineData("{\"negotiateVersion\":123,\"connectionId\":\"123\",\"connectionToken\":\"789\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null, 123, "789")] + [InlineData("{\"negotiateVersion\":123,\"negotiateVersion\":321, \"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 321, "789")] + [InlineData("{\"ignore\":123,\"negotiateVersion\":123, \"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[]}", "123", new string[0], null, null, 123, "789")] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[],\"negotiateVersion\":123, \"connectionToken\":\"789\"}", "123", new string[0], null, null, 123, "789")] + [InlineData("{\"connectionId\":\"123\",\"connectionToken\":\"789\",\"availableTransports\":[]}", "123", new string[0], null, null, 0, "789")] + [InlineData("{\"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[],\"negotiateVersion\":123}", "123", new string[0], null, null, 123, "789")] + [InlineData("{\"connectionToken\":\"789\",\"connectionId\":\"123\",\"availableTransports\":[],\"negotiateVersion\":123, \"connectionToken\":\"987\"}", "123", new string[0], null, null, 123, "987")] + public void ParsingNegotiateResponseMessageSuccessForValid(string json, string connectionId, string[] availableTransports, string url, string accessToken, int version, string connectionToken) { var responseData = Encoding.UTF8.GetBytes(json); var response = NegotiateProtocol.ParseResponse(responseData); @@ -27,6 +35,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests Assert.Equal(availableTransports?.Length, response.AvailableTransports?.Count); Assert.Equal(url, response.Url); Assert.Equal(accessToken, response.AccessToken); + Assert.Equal(version, response.Version); + Assert.Equal(connectionToken, response.ConnectionToken); if (response.AvailableTransports != null) { @@ -44,6 +54,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests [InlineData("{\"connectionId\":\"123\",\"availableTransports\":null}", "Unexpected JSON Token Type 'Null'. Expected a JSON Array.")] [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transferFormats\":[]}]}", "Missing required property 'transport'.")] [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\"}]}", "Missing required property 'transferFormats'.")] + [InlineData("{\"connectionId\":\"123\",\"negotiateVersion\":123,\"availableTransports\":[]}", "Missing required property 'connectionToken'.")] public void ParsingNegotiateResponseMessageThrowsForInvalid(string payload, string expectedMessage) { var responseData = Encoding.UTF8.GetBytes(payload); @@ -82,7 +93,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests string json = Encoding.UTF8.GetString(writer.ToArray()); - Assert.Equal("{\"availableTransports\":[]}", json); + Assert.Equal("{\"negotiateVersion\":0,\"availableTransports\":[]}", json); } } @@ -101,7 +112,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests string json = Encoding.UTF8.GetString(writer.ToArray()); - Assert.Equal("{\"availableTransports\":[{\"transport\":null,\"transferFormats\":[]}]}", json); + Assert.Equal("{\"negotiateVersion\":0,\"availableTransports\":[{\"transport\":null,\"transferFormats\":[]}]}", json); } } } diff --git a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj index 27ac41a9df..2dd22c44a5 100644 --- a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj +++ b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs index 884e427b68..28596c6210 100644 --- a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.ExceptionServices; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; @@ -551,19 +552,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol writer.WriteStartArray(ArgumentsPropertyNameBytes); foreach (var argument in arguments) { - var type = argument?.GetType(); - if (type == typeof(DateTime)) - { - writer.WriteStringValue((DateTime)argument); - } - else if (type == typeof(DateTimeOffset)) - { - writer.WriteStringValue((DateTimeOffset)argument); - } - else - { - JsonSerializer.Serialize(writer, argument, type, _payloadSerializerOptions); - } + JsonSerializer.Serialize(writer, argument, argument?.GetType(), _payloadSerializerOptions); } writer.WriteEndArray(); } @@ -746,19 +735,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol internal static JsonSerializerOptions CreateDefaultSerializerSettings() { - var options = new JsonSerializerOptions(); - options.WriteIndented = false; - options.ReadCommentHandling = JsonCommentHandling.Disallow; - options.AllowTrailingCommas = false; - options.IgnoreNullValues = false; - options.IgnoreReadOnlyProperties = false; - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.PropertyNameCaseInsensitive = true; - options.MaxDepth = 64; - options.DictionaryKeyPolicy = null; - options.DefaultBufferSize = 16 * 1024; - - return options; + return new JsonSerializerOptions() + { + WriteIndented = false, + ReadCommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false, + IgnoreNullValues = false, + IgnoreReadOnlyProperties = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + MaxDepth = 64, + DictionaryKeyPolicy = null, + DefaultBufferSize = 16 * 1024, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; } } } diff --git a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs index 1dc980d750..c05c0397e6 100644 --- a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs +++ b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Text.Encodings.Web; using System.Text.Json; namespace Microsoft.AspNetCore.Internal @@ -20,7 +21,13 @@ namespace Microsoft.AspNetCore.Internal public ReusableUtf8JsonWriter(IBufferWriter stream) { - _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true }); + _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() + { +#if !DEBUG + SkipValidation = true, +#endif + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); } public static ReusableUtf8JsonWriter Get(IBufferWriter stream) diff --git a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj index fa97b3e082..eab77e2bf1 100644 --- a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs index 27560c502a..9a88d32a57 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs @@ -15,6 +15,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"protocol\":\"dummy\",\"version\":1}\u001e", "dummy", 1)] [InlineData("{\"protocol\":\"\",\"version\":10}\u001e", "", 10)] [InlineData("{\"protocol\":\"\",\"version\":10,\"unknown\":null}\u001e", "", 10)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1}\u001e", "secondProtocol", 1)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1,\"version\":75}\u001e", "secondProtocol", 75)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1,\"ignoredField\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1}{\"protocol\":\"wrong\",\"version\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] + [InlineData("{\"\\u0070rotoco\\u006c\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] public void ParsingHandshakeRequestMessageSuccessForValidMessages(string json, string protocol, int version) { var message = new ReadOnlySequence(Encoding.UTF8.GetBytes(json)); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs index a2f696ab17..6f9ba5cb60 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; @@ -28,7 +29,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol PayloadSerializerOptions = new JsonSerializerOptions() { IgnoreNullValues = ignoreNullValues, - PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null + PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, } }; @@ -39,6 +41,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Error reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Number'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Number.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Error reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -101,98 +104,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal(expectedMessage, message); } - [Fact] - public void ReadCaseInsensitivePropertiesByDefault() - { - var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); - - var binder = new TestBinder(null, typeof(TemporaryCustomObject)); - var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); - JsonHubProtocol.TryParseMessage(ref data, binder, out var message); - - var streamItemMessage = Assert.IsType(message); - Assert.Equal(new TemporaryCustomObject() - { - ByteArrProp = new byte[] { 2, 4, 6 }, - IntProp = 43, - DoubleProp = 3.14159, - StringProp = "test", - DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") - }, streamItemMessage.Item); - } - public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20\\u002B12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } })), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); } - - // Revert back to CustomObject when initial values on arrays are supported - // e.g. byte[] arr { get; set; } = byte[] { 1, 2, 3 }; - internal class TemporaryCustomObject : IEquatable - { - // Not intended to be a full set of things, just a smattering of sample serializations - public string StringProp { get; set; } = "SignalR!"; - - public double DoubleProp { get; set; } = 6.2831853071; - - public int IntProp { get; set; } = 42; - - public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11, 0, 0, 0, DateTimeKind.Utc); - - public object NullProp { get; set; } = null; - - public byte[] ByteArrProp { get; set; } - - public override bool Equals(object obj) - { - return obj is TemporaryCustomObject o && Equals(o); - } - - public override int GetHashCode() - { - // This is never used in a hash table - return 0; - } - - public bool Equals(TemporaryCustomObject right) - { - // This allows the comparer below to properly compare the object in the test. - return string.Equals(StringProp, right.StringProp, StringComparison.Ordinal) && - DoubleProp == right.DoubleProp && - IntProp == right.IntProp && - DateTime.Equals(DateTimeProp, right.DateTimeProp) && - NullProp == right.NullProp && - System.Linq.Enumerable.SequenceEqual(ByteArrProp, right.ByteArrProp); - } - } } diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs index d0ef4a6e7f..1570b54462 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs @@ -40,12 +40,30 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("InvocationMessage_HasStreamAndNormalArgument", new InvocationMessage(null, "Target", new object[] { 42 }, new string[] { "__test_id__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[42],\"streamIds\":[\"__test_id__\"]}"), new JsonProtocolTestData("InvocationMessage_HasMultipleStreams", new InvocationMessage(null, "Target", Array.Empty(), new string[] { "__test_id__", "__test_id2__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\",\"__test_id2__\"]}"), new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_HasNonAsciiArgument", new InvocationMessage("Method", new object[] { "מחרוזת כלשהי" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"מחרוזת כלשהי\"]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"), new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"), new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"), new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), + new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasIntegerResult", CompletionMessage.WithResult("123", 1), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"), new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"), new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"), @@ -53,6 +71,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), + new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo" }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\"]}"), new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), @@ -156,8 +179,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\"}", "Missing required property 'arguments'.")] [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}", "Expected 'arguments' to be of type Array.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] public void InvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -270,6 +292,26 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal("foo", bindingFailure.Target); } + [Fact] + public void ReadCaseInsensitivePropertiesByDefault() + { + var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); + + var binder = new TestBinder(null, typeof(CustomObject)); + var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); + JsonHubProtocol.TryParseMessage(ref data, binder, out var message); + + var streamItemMessage = Assert.IsType(message); + Assert.Equal(new CustomObject() + { + ByteArrProp = new byte[] { 2, 4, 6 }, + IntProp = 43, + DoubleProp = 3.14159, + StringProp = "test", + DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") + }, streamItemMessage.Item); + } + public static string Frame(string input) { var data = Encoding.UTF8.GetBytes(input); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs index 255cdead83..de10b1dbf6 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Unexpected end when reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Integer.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -93,35 +94,13 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs index b1fe8c76fb..6b7a5563d3 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.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; @@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol } [Fact] - private void WriteMultiByteCharactersToSmallBuffers() + public void WriteMultiByteCharactersToSmallBuffers() { // Test string breakdown (char => UTF-8 hex values): // a => 61 diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs index 732ccb6af3..84027fff16 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.SignalR.Crankier.Commands { @@ -11,5 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Crankier.Commands public static readonly int NumberOfConnections = 10_000; public static readonly int SendDurationInSeconds = 300; public static readonly HttpTransportType TransportType = HttpTransportType.WebSockets; + public static readonly LogLevel LogLevel = LogLevel.None; } } diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs new file mode 100644 index 0000000000..cb1ef585b6 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs @@ -0,0 +1,60 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.CommandLineUtils; +using static Microsoft.AspNetCore.SignalR.Crankier.Commands.CommandLineUtilities; +using System.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.SignalR.Crankier.Server; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Commands +{ + internal class ServerCommand + { + public static void Register(CommandLineApplication app) + { + app.Command("server", cmd => + { + var logLevelOption = cmd.Option("--log ", "The LogLevel to use.", CommandOptionType.SingleValue); + + cmd.OnExecute(() => + { + LogLevel logLevel = Defaults.LogLevel; + + if (logLevelOption.HasValue() && !Enum.TryParse(logLevelOption.Value(), out logLevel)) + { + return InvalidArg(logLevelOption); + } + return Execute(logLevel); + }); + }); + } + + private static int Execute(LogLevel logLevel) + { + Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); + + var config = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .ConfigureLogging(loggerFactory => + { + loggerFactory.AddConsole().SetMinimumLevel(logLevel); + }) + .UseKestrel() + .UseStartup(); + + host.Build().Run(); + + return 0; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj index 1c1b18a058..1bc1e98bd6 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj +++ b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj @@ -9,6 +9,11 @@ + + + + + diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs index a3443ecd94..93f53ea328 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.Crankier LocalCommand.Register(app); AgentCommand.Register(app); WorkerCommand.Register(app); + ServerCommand.Register(app); app.Command("help", cmd => { diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md index 20f56cc720..7b0a95fbe9 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Readme.md +++ b/src/SignalR/perf/benchmarkapps/Crankier/Readme.md @@ -4,6 +4,24 @@ Load testing for ASP.NET Core SignalR ## Commands +### server + +The `server` command runs a web host exposing a single SignalR `Hub` endpoint on `/echo`. After the first client connection, the server will periodically write concurrent connection information to the console. + +``` +> dotnet run -- help server + +Usage: server [options] + +Options: + --log The LogLevel to use. +``` + +Notes: + +* `LOG_LEVEL` switches internal logging only, not concurrent connection information, and defaults to `LogLevel.None`. Use this option to control Kestrel / SignalR Warnings & Errors being logged to console. + + ### local The `local` command launches a set of local worker clients to establish connections to your SignalR server. @@ -31,13 +49,19 @@ Notes: #### Examples -Attempt to make 10,000 connections to the `echo` hub using WebSockets and 10 workers: +Run the server: + +``` +dotnet run -- server +``` + +Attempt to make 10,000 connections to the server using WebSockets and 10 workers: ``` dotnet run -- local --target-url https://localhost:5001/echo --workers 10 ``` -Attempt to make 5,000 connections to the `echo` hub using Long Polling +Attempt to make 5,000 connections to the server using Long Polling ``` dotnet run -- local --target-url https://localhost:5001/echo --connections 5000 --transport LongPolling diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs new file mode 100644 index 0000000000..1ab6a25abc --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs @@ -0,0 +1,60 @@ +// 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; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounter + { + private int _totalConnectedCount; + private int _peakConnectedCount; + private int _totalDisconnectedCount; + private int _receivedCount; + + private readonly object _lock = new object(); + + public ConnectionSummary Summary + { + get + { + lock (_lock) + { + return new ConnectionSummary + { + CurrentConnections = _totalConnectedCount - _totalDisconnectedCount, + PeakConnections = _peakConnectedCount, + TotalConnected = _totalConnectedCount, + TotalDisconnected = _totalDisconnectedCount, + ReceivedCount = _receivedCount + }; + } + } + } + + public void Receive(string payload) + { + lock (_lock) + { + _receivedCount += payload.Length; + } + } + + public void Connected() + { + lock (_lock) + { + _totalConnectedCount++; + _peakConnectedCount = Math.Max(_totalConnectedCount - _totalDisconnectedCount, _peakConnectedCount); + } + } + + public void Disconnected() + { + lock (_lock) + { + _totalDisconnectedCount++; + } + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs new file mode 100644 index 0000000000..44b8bb26f2 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs @@ -0,0 +1,79 @@ +// 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.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounterHostedService : IHostedService, IDisposable + { + private Stopwatch _timeSinceFirstConnection; + private readonly ConnectionCounter _counter; + private ConnectionSummary _lastSummary; + private Timer _timer; + private int _executingDoWork; + + public ConnectionCounterHostedService(ConnectionCounter counter) + { + _counter = counter; + _timeSinceFirstConnection = new Stopwatch(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + if (Interlocked.Exchange(ref _executingDoWork, 1) == 0) + { + var summary = _counter.Summary; + + if (summary.PeakConnections > 0) + { + if (_timeSinceFirstConnection.ElapsedTicks == 0) + { + _timeSinceFirstConnection.Start(); + } + + var elapsed = _timeSinceFirstConnection.Elapsed; + + if (_lastSummary != null) + { + Console.WriteLine(@"[{0:hh\:mm\:ss}] Current: {1}, peak: {2}, connected: {3}, disconnected: {4}, rate: {5}/s", + elapsed, + summary.CurrentConnections, + summary.PeakConnections, + summary.TotalConnected - _lastSummary.TotalConnected, + summary.TotalDisconnected - _lastSummary.TotalDisconnected, + summary.CurrentConnections - _lastSummary.CurrentConnections + ); + } + + _lastSummary = summary; + } + + Interlocked.Exchange(ref _executingDoWork, 0); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs new file mode 100644 index 0000000000..83f38aaf62 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs @@ -0,0 +1,18 @@ +// 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. + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionSummary + { + public int TotalConnected { get; set; } + + public int TotalDisconnected { get; set; } + + public int PeakConnections { get; set; } + + public int CurrentConnections { get; set; } + + public int ReceivedCount { get; set; } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs new file mode 100644 index 0000000000..0b24b46e9b --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs @@ -0,0 +1,72 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class EchoHub : Hub + { + private ConnectionCounter _counter; + + public EchoHub(ConnectionCounter counter) + { + _counter = counter; + } + + public async Task Broadcast(int duration) + { + var sent = 0; + try + { + var t = new CancellationTokenSource(); + t.CancelAfter(TimeSpan.FromSeconds(duration)); + while (!t.IsCancellationRequested && !Context.ConnectionAborted.IsCancellationRequested) + { + await Clients.All.SendAsync("send", DateTime.UtcNow); + sent++; + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + Console.WriteLine("Broadcast exited: Sent {0} messages", sent); + } + + public override Task OnConnectedAsync() + { + _counter?.Connected(); + return Task.CompletedTask; + } + + public override Task OnDisconnectedAsync(Exception exception) + { + _counter?.Disconnected(); + return Task.CompletedTask; + } + + public DateTime Echo(DateTime time) + { + return time; + } + + public Task EchoAll(DateTime time) + { + return Clients.All.SendAsync("send", time); + } + + public void SendPayload(string payload) + { + _counter?.Receive(payload); + } + + public DateTime GetCurrentTime() + { + return DateTime.UtcNow; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs new file mode 100644 index 0000000000..d8d5efc5bf --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs @@ -0,0 +1,39 @@ +// 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.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class Startup + { + private readonly IConfiguration _config; + public Startup(IConfiguration configuration) + { + _config = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + var signalrBuilder = services.AddSignalR() + .AddMessagePackProtocol(); + + services.AddSingleton(); + + services.AddHostedService(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/echo"); + }); + } + } +} diff --git a/src/SignalR/publish-apps.ps1 b/src/SignalR/publish-apps.ps1 index fcb8c99cae..8c7b2de4a4 100644 --- a/src/SignalR/publish-apps.ps1 +++ b/src/SignalR/publish-apps.ps1 @@ -1,4 +1,4 @@ -param($RootDirectory = (Get-Location), $Framework = "netcoreapp3.1", $Runtime = "win-x64", $CommitHash, $BranchName, $BuildNumber) +param($RootDirectory = (Get-Location), $Framework = "netcoreapp5.0", $Runtime = "win-x64", $CommitHash, $BranchName, $BuildNumber) # De-Powershell the path $RootDirectory = (Convert-Path $RootDirectory) diff --git a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs index 3c835ab933..a8a9fe2095 100644 --- a/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs +++ b/src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.SignalR return SendToAllConnections(methodName, args, null); } - private Task SendToAllConnections(string methodName, object[] args, Func include) + private Task SendToAllConnections(string methodName, object[] args, Func include, object state = null) { List tasks = null; SerializedHubMessage message = null; @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.SignalR // foreach over HubConnectionStore avoids allocating an enumerator foreach (var connection in _connections) { - if (include != null && !include(connection)) + if (include != null && !include(connection, state)) { continue; } @@ -127,12 +127,12 @@ namespace Microsoft.AspNetCore.SignalR // Tasks and message are passed by ref so they can be lazily created inside the method post-filtering, // while still being re-usable when sending to multiple groups - private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, ref List tasks, ref SerializedHubMessage message) + private void SendToGroupConnections(string methodName, object[] args, ConcurrentDictionary connections, Func include, object state, ref List tasks, ref SerializedHubMessage message) { // foreach over ConcurrentDictionary avoids allocating an enumerator foreach (var connection in connections) { - if (include != null && !include(connection.Value)) + if (include != null && !include(connection.Value, state)) { continue; } @@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.SignalR // group might be modified inbetween checking and sending List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message); if (tasks != null) { @@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.SignalR var group = _groups[groupName]; if (group != null) { - SendToGroupConnections(methodName, args, group, null, ref tasks, ref message); + SendToGroupConnections(methodName, args, group, null, null, ref tasks, ref message); } } @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.SignalR List tasks = null; SerializedHubMessage message = null; - SendToGroupConnections(methodName, args, group, connection => !excludedConnectionIds.Contains(connection.ConnectionId), ref tasks, ref message); + SendToGroupConnections(methodName, args, group, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds, ref tasks, ref message); if (tasks != null) { @@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendUserAsync(string userId, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => string.Equals(connection.UserIdentifier, userId, StringComparison.Ordinal)); + return SendToAllConnections(methodName, args, (connection, state) => string.Equals(connection.UserIdentifier, (string)state, StringComparison.Ordinal), userId); } /// @@ -292,19 +292,19 @@ namespace Microsoft.AspNetCore.SignalR /// public override Task SendAllExceptAsync(string methodName, object[] args, IReadOnlyList excludedConnectionIds, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => !excludedConnectionIds.Contains(connection.ConnectionId)); + return SendToAllConnections(methodName, args, (connection, state) => !((IReadOnlyList)state).Contains(connection.ConnectionId), excludedConnectionIds); } /// public override Task SendConnectionsAsync(IReadOnlyList connectionIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => connectionIds.Contains(connection.ConnectionId)); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.ConnectionId), connectionIds); } /// public override Task SendUsersAsync(IReadOnlyList userIds, string methodName, object[] args, CancellationToken cancellationToken = default) { - return SendToAllConnections(methodName, args, connection => userIds.Contains(connection.UserIdentifier)); + return SendToAllConnections(methodName, args, (connection, state) => ((IReadOnlyList)state).Contains(connection.UserIdentifier), userIds); } } } diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs index b0a841e1b7..71ea5a687e 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal { if (descriptor.OriginalParameterTypes[parameterPointer] == typeof(CancellationToken)) { - cts = CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted); + cts = CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted, default); arguments[parameterPointer] = cts.Token; } else if (isStreamCall && ReflectionHelper.IsStreamingType(descriptor.OriginalParameterTypes[parameterPointer], mustBeDirectType: true)) @@ -308,7 +308,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal return; } - cts = cts ?? CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted); + cts = cts ?? CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted, default); connection.ActiveRequestCancellationSources.TryAdd(hubMethodInvocationMessage.InvocationId, cts); var enumerable = descriptor.FromReturnedStream(result, cts.Token); diff --git a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs index 511280d636..a333c600a8 100644 --- a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs +++ b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs @@ -132,6 +132,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal methodBuilder.DefineGenericParameters(genericTypeNames); } + // Check to see if the last parameter of the method is a CancellationToken + bool hasCancellationToken = paramTypes.LastOrDefault() == typeof(CancellationToken); + if (hasCancellationToken) + { + // remove CancellationToken from input paramTypes + paramTypes = paramTypes.Take(paramTypes.Length - 1).ToArray(); + } + var generator = methodBuilder.GetILGenerator(); // Declare local variable to store the arguments to IClientProxy.SendCoreAsync @@ -145,7 +153,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal generator.Emit(OpCodes.Ldstr, interfaceMethodInfo.Name); // Create an new object array to hold all the parameters to this method - generator.Emit(OpCodes.Ldc_I4, parameters.Length); // Stack: + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); // Stack: generator.Emit(OpCodes.Newarr, typeof(object)); // allocate object array generator.Emit(OpCodes.Stloc_0); @@ -162,8 +170,16 @@ namespace Microsoft.AspNetCore.SignalR.Internal // Load parameter array on to the stack. generator.Emit(OpCodes.Ldloc_0); - // Get 'CancellationToken.None' and put it on the stack, since we don't support CancellationToken right now - generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + if (hasCancellationToken) + { + // Get CancellationToken from input argument and put it on the stack + generator.Emit(OpCodes.Ldarg, paramTypes.Length + 1); + } + else + { + // Get 'CancellationToken.None' and put it on the stack, for when method does not have CancellationToken + generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod); + } // Send! generator.Emit(OpCodes.Callvirt, invokeMethod); diff --git a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs index 4f68f6fe74..9f412af8f4 100644 --- a/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs +++ b/src/SignalR/server/SignalR/test/Internal/TypedClientBuilderTests.cs @@ -75,6 +75,41 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal await task2.OrTimeout(); } + [Fact] + public async Task SupportsCancellationToken() + { + var clientProxy = new MockProxy(); + var typedProxy = TypedClientBuilder.Build(clientProxy); + CancellationTokenSource cts1 = new CancellationTokenSource(); + var task1 = typedProxy.Method("foo", cts1.Token); + Assert.False(task1.IsCompleted); + + CancellationTokenSource cts2 = new CancellationTokenSource(); + var task2 = typedProxy.NoArgumentMethod(cts2.Token); + Assert.False(task2.IsCompleted); + + Assert.Collection(clientProxy.Sends, + send1 => + { + Assert.Equal("Method", send1.Method); + Assert.Equal(1, send1.Arguments.Length); + Assert.Collection(send1.Arguments, + arg1 => Assert.Equal("foo", arg1)); + Assert.Equal(cts1.Token, send1.CancellationToken); + send1.Complete(); + }, + send2 => + { + Assert.Equal("NoArgumentMethod", send2.Method); + Assert.Equal(0, send2.Arguments.Length); + Assert.Equal(cts2.Token, send2.CancellationToken); + send2.Complete(); + }); + + await task1.OrTimeout(); + await task2.OrTimeout(); + } + [Fact] public void ThrowsIfProvidedAClass() { @@ -179,6 +214,12 @@ namespace Microsoft.AspNetCore.SignalR.Tests.Internal Task SubMethod(string foo); } + public interface ICancellationTokenMethod + { + Task Method(string foo, CancellationToken cancellationToken); + Task NoArgumentMethod(CancellationToken cancellationToken); + } + public interface IPropertiesClient { string Property { get; } diff --git a/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs new file mode 100644 index 0000000000..6c3ba450d8 --- /dev/null +++ b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Http.Connections.Client; +using Xunit; +using Constants = Microsoft.AspNetCore.Http.Connections.Client.Internal.Constants; + +namespace Microsoft.AspNetCore.Http.Connections.Tests +{ + public class UserAgentHeaderTest + { + [Fact] + public void UserAgentHeaderIsAccurate() + { + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + var version = typeof(HttpConnection).Assembly.GetName().Version; + var os = RuntimeInformation.OSDescription; + var runtime = ".NET"; + var runtimeVersion = RuntimeInformation.FrameworkDescription; + var assemblyVersion = typeof(Constants) + .Assembly + .GetCustomAttributes() + .FirstOrDefault(); + var userAgent = Constants.UserAgentHeader; + var expectedUserAgent = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({assemblyVersion.InformationalVersion}; {os}; {runtime}; {runtimeVersion})"; + + Assert.Equal(expectedUserAgent, userAgent); + } + } +} diff --git a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs index a1594b0fd3..b6f276ab5e 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs +++ b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * Acks are sent to the Acknowledgement channel. // * See the Write[type] methods for a description of the protocol for each in-depth. // * The "Variable length integer" is the length-prefixing format used by BinaryReader/BinaryWriter: - // * https://docs.microsoft.com/en-us/dotnet/api/system.io.binarywriter.write?view=netstandard-2.0 + // * https://docs.microsoft.com/dotnet/api/system.io.binarywriter.write?view=netcore-2.2 // * The "Length prefixed string" is the string format used by BinaryReader/BinaryWriter: // * A 7-bit variable length integer encodes the length in bytes, followed by the encoded string in UTF-8. diff --git a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj index 4ef7283db6..82e665d1c0 100644 --- a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj +++ b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj @@ -24,6 +24,8 @@ + + diff --git a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs index d230e1bb8d..7e33386d27 100644 --- a/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs +++ b/src/Tools/Microsoft.dotnet-openapi/test/OpenApiTestBase.cs @@ -21,8 +21,7 @@ namespace Microsoft.DotNet.OpenApi.Tests protected readonly TextWriter _output = new StringWriter(); protected readonly TextWriter _error = new StringWriter(); protected readonly ITestOutputHelper _outputHelper; - protected const string TestTFM = "netcoreapp3.1"; - + protected const string TestTFM = "netcoreapp5.0"; protected const string Content = @"{""x-generator"": ""NSwag""}"; protected const string ActualUrl = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/api-with-examples.yaml"; diff --git a/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs b/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs index adcbe32b1e..5a42903675 100644 --- a/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs +++ b/src/Tools/dotnet-user-secrets/test/UserSecretsTestFixture.cs @@ -35,7 +35,7 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests private const string ProjectTemplate = @" Exe - netcoreapp3.1 + netcoreapp5.0 {0} false diff --git a/src/Tools/dotnet-watch/test/ProgramTests.cs b/src/Tools/dotnet-watch/test/ProgramTests.cs index 0e7dff9b82..6e24eb13d2 100644 --- a/src/Tools/dotnet-watch/test/ProgramTests.cs +++ b/src/Tools/dotnet-watch/test/ProgramTests.cs @@ -28,7 +28,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests { _tempDir .WithCSharpProject("testproj") - .WithTargetFrameworks("netcoreapp3.1") + .WithTargetFrameworks("netcoreapp5.0") .Dir() .WithFile("Program.cs") .Create(); diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj index d0cde953c7..7399c1018d 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe true diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj index 9f015b1ee4..8f8043d0de 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe false true diff --git a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj index af6de1b33f..6de103d382 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj @@ -9,7 +9,7 @@ Exe - netcoreapp3.1 + netcoreapp5.0 true diff --git a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj index 95412443e6..110ff7686b 100644 --- a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj +++ b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5.0 exe true