diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 51ce25fea8..5f44f14fc3 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -433,7 +433,8 @@ stages: jobName: Linux_musl_x64_build jobDisplayName: "Build: Linux Musl x64" agentOs: Linux - buildScript: ./dockerbuild.sh alpine + container: mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.9-WithNode-0fc54a3-20190918214015 + buildScript: ./build.sh buildArgs: --arch x64 --os-name linux-musl @@ -448,6 +449,7 @@ stages: $(_InternalRuntimeDownloadArgs) installNodeJs: false installJdk: false + skipComponentGovernanceDetection: true artifacts: - name: Linux_musl_x64_Logs path: artifacts/log/ @@ -466,7 +468,8 @@ stages: jobDisplayName: "Build: Linux Musl ARM64" agentOs: Linux useHostedUbuntu: false - buildScript: ./dockerbuild.sh ubuntu-alpine37 + container: mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-cross-arm64-alpine-20200413125008-406629a + buildScript: ./build.sh buildArgs: --arch arm64 --os-name linux-musl diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml index 3906f93cdf..e13f4bfb55 100644 --- a/.azure/pipelines/jobs/default-build.yml +++ b/.azure/pipelines/jobs/default-build.yml @@ -35,7 +35,8 @@ # Specifies what directory to run build.sh/cmd # buildScript: string # Specifies the build script to run. Defaults to build.sh or build.cmd. - +# skipComponentGovernanceDetection: boolean +# Determines if component governance detection can be skipped # # See https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema for details # @@ -60,6 +61,7 @@ parameters: installJdk: true timeoutInMinutes: 180 useHostedUbuntu: true + skipComponentGovernanceDetection: false # We need longer than the default amount of 5 minutes to upload our logs/artifacts. (We currently take around 5 mins in the best case). # This makes sure we have time to upload everything in the case of a build timeout - really important for investigating a build @@ -114,6 +116,8 @@ jobs: name: NetCoreInternal-Pool # Visual Studio Enterprise - contains some stuff, like SQL Server and IIS Express, that we use for testing queue: BuildPool.Server.Amd64.VS2019 + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} variables: - AgentOsName: ${{ parameters.agentOs }} - ASPNETCORE_TEST_LOG_MAXPATH: "200" # Keep test log file name length low enough for artifact zipping @@ -238,7 +242,7 @@ jobs: continueOnError: true condition: always() - - ${{ if and(startsWith(parameters.jobDisplayName, 'Build:'), ne(variables['skipComponentGovernanceDetection'], 'true'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if and(startsWith(parameters.jobDisplayName, 'Build:'), ne(variables['skipComponentGovernanceDetection'], 'true'), ne(parameters.skipComponentGovernanceDetection, 'true'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 condition: and(succeeded(), ne(variables['CG_RAN'], 'true')) displayName: 'Component Detection' diff --git a/.azure/pipelines/quarantined-pr.yml b/.azure/pipelines/quarantined-pr.yml new file mode 100644 index 0000000000..d6e4b61ed2 --- /dev/null +++ b/.azure/pipelines/quarantined-pr.yml @@ -0,0 +1,149 @@ +# We want to run quarantined tests on master as well as on PRs +trigger: + batch: true + branches: + include: + - master + +schedules: +- cron: "0 */4 * * *" + displayName: Every 4 hours test run + branches: + include: + - master + always: true + +variables: +- ${{ if ne(variables['System.TeamProject'], 'internal') }}: + - name: _UseHelixOpenQueues + value: 'true' +- ${{ if eq(variables['System.TeamProject'], 'internal') }}: + - group: DotNet-HelixApi-Access + - name: _UseHelixOpenQueues + value: 'false' + +jobs: +- template: jobs/default-build.yml + parameters: + jobName: Helix_quarantined_x64 + jobDisplayName: 'Tests: Helix' + agentOs: Windows + timeoutInMinutes: 240 + steps: + # Build the shared framework + - script: ./build.cmd -ci -nobl -all -pack -arch x64 /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log + displayName: Build shared fx + - script: ./build.cmd -ci -nobl -noBuildRepoTasks -restore -noBuild -noBuildNative -projects src/Grpc/**/*.csproj + displayName: Restore interop projects + - script: ./build.cmd -ci -nobl -noBuildRepoTasks -noRestore -test -all -noBuildJava -noBuildNative + -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true + /p:BuildInteropProjects=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log + displayName: Run build.cmd helix target + continueOnError: true + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + artifacts: + - name: Helix_logs + path: artifacts/log/ + publishOnError: true + +- template: jobs/default-build.yml + parameters: + jobName: Windows_Quarantined_x64 + jobDisplayName: 'Tests: Windows x64' + agentOs: Windows + timeoutInMinutes: 240 + isTestingJob: true + steps: + - powershell: "& ./build.ps1 -CI -nobl -all -pack -NoBuildJava" + displayName: Build + # The templates part can be removed when the Blazor Templates run on Helix + - script: ./src/ProjectTemplates/build.cmd -ci -nobl -pack -NoRestore -NoBuilddeps "/p:RunTemplateTests=true" + displayName: Pack Templates + - script: ./build.cmd -ci -nobl -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /p:RunQuarantinedTests=true /p:SkipHelixReadyTests=true" + displayName: Run Quarantined Tests + continueOnError: true + - task: PublishTestResults@2 + displayName: Publish Quarantined Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' + condition: always() + artifacts: + - name: Windows_Quarantined_Test_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + - name: Windows_Quarantined_Test_Results + path: artifacts/TestResults/ + publishOnError: true + includeForks: true + +- template: jobs/default-build.yml + parameters: + jobName: MacOS_Quarantined_Test + jobDisplayName: "Tests: macOS 10.14" + agentOs: macOS + timeoutInMinutes: 240 + isTestingJob: true + steps: + - bash: ./build.sh --all --pack --ci --nobl --no-build-java + displayName: Build + # The templates part can be removed when the Blazor Templates run on Helix + - bash: ./src/ProjectTemplates/build.sh --ci --nobl --pack --no-restore --no-build-deps + displayName: Pack Templates (for Template tests) + - bash: ./build.sh --no-build --ci --nobl --test -p:RunTemplateTests=true -p:RunQuarantinedTests=true -p:SkipHelixReadyTests=true + displayName: Run Quarantined Tests + continueOnError: true + - task: PublishTestResults@2 + displayName: Publish Quarantined Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' + condition: always() + artifacts: + - name: MacOS_Quarantined_Test_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + - name: MacOS_Quarantined_Test_Results + path: artifacts/TestResults/ + publishOnError: true + includeForks: true + +- template: jobs/default-build.yml + parameters: + jobName: Linux_Quarantined_Test + jobDisplayName: "Tests: Ubuntu 16.04 x64" + agentOs: Linux + timeoutInMinutes: 240 + isTestingJob: true + useHostedUbuntu: false + steps: + - bash: ./build.sh --all --pack --ci --nobl --no-build-java + displayName: Build + # The templates part can be removed when the Blazor Templates run on Helix + - bash: ./src/ProjectTemplates/build.sh --ci --nobl --pack --no-restore --no-build-deps + displayName: Pack Templates (for Template tests) + - bash: ./build.sh --no-build --ci --nobl --test -p:RunTemplateTests=true -p:RunQuarantinedTests=true -p:SkipHelixReadyTests=true + displayName: Run Quarantined Tests + continueOnError: true + - task: PublishTestResults@2 + displayName: Publish Quarantined Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' + condition: always() + artifacts: + - name: Linux_Quarantined_Test_Logs + path: artifacts/log/ + publishOnError: true + includeForks: true + - name: Linux_Quarantined_Test_Results + path: artifacts/TestResults/ + publishOnError: true + includeForks: true diff --git a/.azure/pipelines/quarantined-tests.yml b/.azure/pipelines/quarantined-tests.yml index b26f0194ca..dec9d844d2 100644 --- a/.azure/pipelines/quarantined-tests.yml +++ b/.azure/pipelines/quarantined-tests.yml @@ -1,13 +1,10 @@ -# We want to run quarantined tests on master as well as on PRs -trigger: - batch: true - branches: - include: - - master +# We only want to run quarantined tests on master +pr: none +trigger: none schedules: -- cron: "0 */4 * * *" - displayName: Every 4 hours test run +- cron: "0 18 * * *" + displayName: Run tests once a day at 11 PM branches: include: - master @@ -28,7 +25,7 @@ jobs: jobName: Helix_quarantined_x64 jobDisplayName: 'Tests: Helix' agentOs: Windows - timeoutInMinutes: 180 + timeoutInMinutes: 480 steps: # Build the shared framework - script: ./build.cmd -ci -nobl -all -pack -arch x64 /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log @@ -36,7 +33,7 @@ jobs: - script: ./build.cmd -ci -nobl -noBuildRepoTasks -restore -noBuild -noBuildNative -projects src/Grpc/**/*.csproj displayName: Restore interop projects - script: ./build.cmd -ci -nobl -noBuildRepoTasks -noRestore -test -all -noBuildJava -noBuildNative - -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true + -projects eng\helix\helix.proj /p:IsHelixDaily=true /p:RunQuarantinedTests=true /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildInteropProjects=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log displayName: Run build.cmd helix target continueOnError: true @@ -48,102 +45,27 @@ jobs: path: artifacts/log/ publishOnError: true +# Helix ARM64 - template: jobs/default-build.yml parameters: - jobName: Windows_Quarantined_x64 - jobDisplayName: 'Tests: Windows x64' - agentOs: Windows - timeoutInMinutes: 180 - isTestingJob: true - steps: - - powershell: "& ./build.ps1 -CI -nobl -all -pack -NoBuildJava" - displayName: Build - # The templates part can be removed when the Blazor Templates run on Helix - - script: ./src/ProjectTemplates/build.cmd -ci -nobl -pack -NoRestore -NoBuilddeps "/p:RunTemplateTests=true" - displayName: Pack Templates - - script: ./build.cmd -ci -nobl -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /p:RunQuarantinedTests=true /p:SkipHelixReadyTests=true" - displayName: Run Quarantined Tests - continueOnError: true - - task: PublishTestResults@2 - displayName: Publish Quarantined Test Results - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' - condition: always() - artifacts: - - name: Windows_Quarantined_Test_Logs - path: artifacts/log/ - publishOnError: true - includeForks: true - - name: Windows_Quarantined_Test_Results - path: artifacts/TestResults/ - publishOnError: true - includeForks: true - -- template: jobs/default-build.yml - parameters: - jobName: MacOS_Quarantined_Test - jobDisplayName: "Tests: macOS 10.14" - agentOs: macOS - timeoutInMinutes: 180 - isTestingJob: true - steps: - - bash: ./build.sh --all --pack --ci --nobl --no-build-java - displayName: Build - # The templates part can be removed when the Blazor Templates run on Helix - - bash: ./src/ProjectTemplates/build.sh --ci --nobl --pack --no-restore --no-build-deps - displayName: Pack Templates (for Template tests) - - bash: ./build.sh --no-build --ci --nobl --test -p:RunTemplateTests=true -p:RunQuarantinedTests=true -p:SkipHelixReadyTests=true - displayName: Run Quarantined Tests - continueOnError: true - - task: PublishTestResults@2 - displayName: Publish Quarantined Test Results - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' - condition: always() - artifacts: - - name: MacOS_Quarantined_Test_Logs - path: artifacts/log/ - publishOnError: true - includeForks: true - - name: MacOS_Quarantined_Test_Results - path: artifacts/TestResults/ - publishOnError: true - includeForks: true - -- template: jobs/default-build.yml - parameters: - jobName: Linux_Quarantined_Test - jobDisplayName: "Tests: Ubuntu 16.04 x64" + jobName: Helix_quarantined_arm64 + jobDisplayName: "Tests: Helix ARM64" agentOs: Linux - timeoutInMinutes: 180 - isTestingJob: true - useHostedUbuntu: false + timeoutInMinutes: 480 steps: - - bash: ./build.sh --all --pack --ci --nobl --no-build-java - displayName: Build - # The templates part can be removed when the Blazor Templates run on Helix - - bash: ./src/ProjectTemplates/build.sh --ci --nobl --pack --no-restore --no-build-deps - displayName: Pack Templates (for Template tests) - - bash: ./build.sh --no-build --ci --nobl --test -p:RunTemplateTests=true -p:RunQuarantinedTests=true -p:SkipHelixReadyTests=true - displayName: Run Quarantined Tests + - script: ./restore.sh -ci -nobl + displayName: Restore + - script: ./build.sh --ci --nobl --noBuildRepoTasks --arch arm64 -test --no-build-nodejs --all --projects + $(Build.SourcesDirectory)/eng/helix/helix.proj /p:IsHelixJob=true /p:IsHelixDaily=true /p:RunQuarantinedTests=true + /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log + displayName: Run build.sh helix arm64 target continueOnError: true - - task: PublishTestResults@2 - displayName: Publish Quarantined Test Results - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Quarantined' - condition: always() + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + installNodeJs: false artifacts: - - name: Linux_Quarantined_Test_Logs + - name: Helix_arm64_logs path: artifacts/log/ publishOnError: true includeForks: true - - name: Linux_Quarantined_Test_Results - path: artifacts/TestResults/ - publishOnError: true - includeForks: true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b168b69468..c5df913b84 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,14 +9,16 @@ /eng/common/ @dotnet-maestro-bot /eng/Versions.props @dotnet-maestro-bot @dougbu /eng/Version.Details.xml @dotnet-maestro-bot @dougbu -/src/Components/ @SteveSandersonMS +/src/Components/ @SteveSandersonMS @aspnet-blazor-eng /src/DefaultBuilder/ @tratcher /src/Hosting/ @tratcher /src/Http/ @tratcher @jkotalik /src/Middleware/ @tratcher /src/Middleware/HttpsPolicy/ @jkotalik /src/Middleware/Rewrite/ @jkotalik -# /src/ProjectTemplates/ @ryanbrandenburg +# /src/ProjectTemplates/ @ryanbrandenburg +/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/ @aspnet-blazor-eng +/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/ @aspnet-blazor-eng /src/Security/ @tratcher /src/Servers/ @tratcher @jkotalik @halter73 /src/Shared/runtime/ @dotnet/http diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md index 835459f296..693dabec86 100644 --- a/docs/BuildFromSource.md +++ b/docs/BuildFromSource.md @@ -68,6 +68,8 @@ To update an existing copy, run: git submodule update --init --recursive ``` +**NOTE** some ISPs have been know to use web filtering software that has caused issues with git repository cloning, if you experience issues cloning this repo please review + ## Building in Visual Studio Before opening our .sln files in Visual Studio or VS Code, you need to perform the following actions. @@ -228,6 +230,16 @@ TargetOsName | The base runtime identifier to build for (win, linux, After building ASP.NET Core from source, you will need to install and use your local version of ASP.NET Core. See ["Artifacts"](./Artifacts.md) for more explanation of the different folders produced by a build. +Building installers does not run as part of `build.cmd` run without parameters, so you should opt-in for building them: + +```ps1 +.\build.cmd -all -pack -arch x64 +.\build.cmd -all -pack -arch x86 -noBuildJava +.\build.cmd -buildInstallers +``` + +*Note*: Additional build steps listed above aren't necessary on Linux or macOS. + * Run the installers produced in `artifacts/installers/{Debug, Release}/` for your platform. * Add a NuGet.Config to your project directory with the following content: diff --git a/docs/Helix.md b/docs/Helix.md index e8c733d5a8..318a9ef8cb 100644 --- a/docs/Helix.md +++ b/docs/Helix.md @@ -20,12 +20,23 @@ This will restore, and then publish all the test project including some bootstra - Required queues: Windows10, OSX, Ubuntu1604 - Full queue matrix: Windows[7, 81, 10], Ubuntu[1604, 1804, 2004], Centos7, Debian[8,9], Redhat7, Fedora28, Arm64 (Win10, Debian9) +- The queues are defined in [Helix.Common.props](https://github.com/dotnet/aspnetcore/blob/master/eng/targets/Helix.Common.props) -aspnetcore-ci runs non quarantined tests against the required helix queues as a required PR check and all builds on all branches. +[aspnetcore-ci](https://dev.azure.com/dnceng/public/_build?definitionId=278) runs non quarantined tests against the required helix queues as a required PR check and all builds on all branches. -aspnetcore-quarantined-tests runs only quarantined tests against the required queues only on master every 4 hours. +[aspnetcore-helix-matrix](https://dev.azure.com/dnceng/public/_build?definitionId=837) runs non quarantined tests against all queues twice a day only on public master. + +[aspnetcore-quarantined-pr](https://dev.azure.com/dnceng/public/_build?definitionId=869) runs only quarantined tests against the required queues on PRs and on master every 4 hours. + +[aspnetcore-quarantined-tests](https://dev.azure.com/dnceng/public/_build?definitionId=331) runs only quarantined tests against all queues only on public master once a day at 11 PM. + +You can always manually queue pipeline runs by clicking on the link to the pipeline -> Run Pipeline -> select your branch/tag and commit + +## Checkin process expectations + +- The normal PR process has aspnetcore-ci will ensure that the required queues are green. +- If your changes are likely to have cross platform impact that would affect more than the required queues, you should kick off a manual aspnetcore-helix-matrix pipeline run against your branch before merging your PR. Even though aspnetcore-helix-matrix is not a required checkin gate, if your changes break this pipeline, you must either immediately revert your changes, or quarantine the test, its never ok to leave this pipeline in a broken state. -aspnetcore-helix-matrix runs non quarantined tests against all queues twice a day only on public master. ## How do I look at the results of a helix run on Azure Pipelines? diff --git a/docs/README.md b/docs/README.md index f055ea32b5..230b8d7f51 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,3 +3,9 @@ Contributor documentation The primary audience for documentation in this folder is contributors to ASP.NET Core. If you are looking for documentation on how to *use* ASP.NET Core, go to . + +# ASP.NET Core developer workflow + +- [Building from source](BuildFromSource.md) +- [Troubleshooting build errors](BuildErrors.md) +- [Artifacts structure](Artifacts.md) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index dd12efa35e..91343d8b13 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -67,7 +67,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4034f92297..0583f07d45 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,288 +13,288 @@ https://github.com/dotnet/blazor cc449601d638ffaab58ae9487f0fd010bb178a12 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/efcore - 0f2b5023539eb0c5a4fc565af7e59b95c252529f + dff6a1abc282bb642042b4fe3a643f5b586bcf03 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 - + https://github.com/dotnet/runtime - b0f97149538e3dabde2267efa326c76a628a30ff + d3e6947f39ee04b0a6a44bfe6ba1310fbc10c7e5 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index 99fd5d6f4e..d6ae64ce4e 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,7 +9,7 @@ 5 0 0 - 7 + 8 @@ -66,79 +66,79 @@ 3.7.0-4.20319.6 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 + 5.0.0-preview.8.20352.6 - 5.0.0-preview.7.20326.1 + 5.0.0-preview.8.20352.6 3.2.0 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 - 5.0.0-preview.7.20326.1 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 + 5.0.0-preview.8.20353.5 2.1.1 2.2.0 @@ -221,7 +221,7 @@ 3.0.0 2.1.90 4.10.0 - 0.10.1 + 0.11.2 1.0.2 12.0.2 diff --git a/eng/docker/alpine.Dockerfile b/eng/docker/alpine.Dockerfile deleted file mode 100644 index bc1547d1df..0000000000 --- a/eng/docker/alpine.Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM microsoft/dotnet:2.1.0-preview1-runtime-deps-alpine -ARG USER -ARG USER_ID -ARG GROUP_ID -ARG WORKDIR - -WORKDIR ${WORKDIR} -RUN mkdir -p "/home/$USER" && chown "${USER_ID}:${GROUP_ID}" "/home/$USER" -ENV HOME "/home/$USER" - -RUN apk add --no-cache \ - bash \ - wget \ - git \ - jq \ - curl \ - icu-libs \ - openssl - -USER $USER_ID:$GROUP_ID - -# Disable the invariant mode (set in base image) -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 - -# Skip package initilization -ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 diff --git a/eng/docker/ubuntu-alpine37.Dockerfile b/eng/docker/ubuntu-alpine37.Dockerfile deleted file mode 100644 index 859098f04e..0000000000 --- a/eng/docker/ubuntu-alpine37.Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-cross-arm64-alpine10fcdcf-20190208200917 -ARG USER -ARG USER_ID -ARG GROUP_ID -ARG WORKDIR - -WORKDIR ${WORKDIR} -RUN mkdir -p "/home/$USER" && chown "${USER_ID}:${GROUP_ID}" "/home/$USER" -ENV HOME "/home/$USER" - -USER $USER_ID:$GROUP_ID - -# Disable the invariant mode (set in base image) -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 - -# Skip package initilization -ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 diff --git a/eng/targets/Helix.Common.props b/eng/targets/Helix.Common.props index 63f8d06a2d..5f8a83cb6d 100644 --- a/eng/targets/Helix.Common.props +++ b/eng/targets/Helix.Common.props @@ -39,7 +39,7 @@ - + diff --git a/src/Components/Components.sln b/src/Components/Components.sln index 722c3a4ea1..cfdbc2bce2 100644 --- a/src/Components/Components.sln +++ b/src/Components/Components.sln @@ -257,8 +257,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{81250121-9B43-40B1-BF11-CE4458F2676C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.HttpHandler", "WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Authentication.Client", "WebAssembly\testassets\Wasm.Authentication.Client\Wasm.Authentication.Client.csproj", "{5F12F7B9-70BE-48F6-922A-3A1E87EE6BF0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Authentication.Shared", "WebAssembly\testassets\Wasm.Authentication.Shared\Wasm.Authentication.Shared.csproj", "{630D5388-7A2F-42DD-9154-1F62A18CBB69}" @@ -1523,18 +1521,6 @@ Global {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.Build.0 = Release|Any CPU {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.ActiveCfg = Release|Any CPU {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.Build.0 = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.ActiveCfg = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.Build.0 = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.ActiveCfg = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.Build.0 = Debug|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.Build.0 = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.ActiveCfg = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.Build.0 = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.ActiveCfg = Release|Any CPU - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.Build.0 = Release|Any CPU {5F12F7B9-70BE-48F6-922A-3A1E87EE6BF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5F12F7B9-70BE-48F6-922A-3A1E87EE6BF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F12F7B9-70BE-48F6-922A-3A1E87EE6BF0}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1687,7 +1673,6 @@ Global {5CD61479-5181-4A5B-B90F-9F34316248B3} = {81250121-9B43-40B1-BF11-CE4458F2676C} {6B0D6C08-FC30-4822-9464-4D24FF4CDC17} = {81250121-9B43-40B1-BF11-CE4458F2676C} {81250121-9B43-40B1-BF11-CE4458F2676C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {031AD67E-DDDE-4A20-874A-8D0A791C6F4C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {5F12F7B9-70BE-48F6-922A-3A1E87EE6BF0} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} {630D5388-7A2F-42DD-9154-1F62A18CBB69} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} {A062ACCE-AB0D-4569-9548-4BC0D9A9174B} = {2FC10057-7A0A-4E34-8302-879925BC0102} diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs index cba973e779..78334efc06 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs @@ -125,8 +125,14 @@ namespace Microsoft.AspNetCore.Components private readonly object _dummy; private readonly int _dummyPrimitive; public ElementReference(string id) { throw null; } + public ElementReference(string id, Microsoft.AspNetCore.Components.ElementReferenceContext? context) { throw null; } + public Microsoft.AspNetCore.Components.ElementReferenceContext? Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } + public abstract partial class ElementReferenceContext + { + protected ElementReferenceContext() { } + } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct EventCallback { @@ -468,6 +474,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public abstract Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get; } + protected internal Microsoft.AspNetCore.Components.ElementReferenceContext? ElementReferenceContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; } diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index a620231de7..ad6c9c07b9 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -125,8 +125,14 @@ namespace Microsoft.AspNetCore.Components private readonly object _dummy; private readonly int _dummyPrimitive; public ElementReference(string id) { throw null; } + public ElementReference(string id, Microsoft.AspNetCore.Components.ElementReferenceContext? context) { throw null; } + public Microsoft.AspNetCore.Components.ElementReferenceContext? Context { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } + public abstract partial class ElementReferenceContext + { + protected ElementReferenceContext() { } + } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct EventCallback { @@ -467,6 +473,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public abstract Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get; } + protected internal Microsoft.AspNetCore.Components.ElementReferenceContext? ElementReferenceContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; } diff --git a/src/Components/Components/src/ElementReference.cs b/src/Components/Components/src/ElementReference.cs index e25c673be7..864c21e07e 100644 --- a/src/Components/Components/src/ElementReference.cs +++ b/src/Components/Components/src/ElementReference.cs @@ -23,13 +23,32 @@ namespace Microsoft.AspNetCore.Components /// public string Id { get; } - public ElementReference(string id) + /// + /// Gets the instance. + /// + public ElementReferenceContext? Context { get; } + + /// + /// Instantiates a new . + /// + /// A unique identifier for this . + /// The nullable instance. + public ElementReference(string id, ElementReferenceContext? context) { Id = id; + Context = context; } - internal static ElementReference CreateWithUniqueId() - => new ElementReference(CreateUniqueId()); + /// + /// Instantiates a new . + /// + /// A unique identifier for this . + public ElementReference(string id) : this(id, null) + { + } + + internal static ElementReference CreateWithUniqueId(ElementReferenceContext? context) + => new ElementReference(CreateUniqueId(), context); private static string CreateUniqueId() { diff --git a/src/Components/Components/src/ElementReferenceContext.cs b/src/Components/Components/src/ElementReferenceContext.cs new file mode 100644 index 0000000000..2bb7b9194a --- /dev/null +++ b/src/Components/Components/src/ElementReferenceContext.cs @@ -0,0 +1,12 @@ +// 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.Components +{ + /// + /// Context for an . + /// + public abstract class ElementReferenceContext + { + } +} diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index 1e7f881ed5..7565534a51 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Components/Components/src/PlatformInfo.cs b/src/Components/Components/src/PlatformInfo.cs index 81338cab58..2ed3ace3b9 100644 --- a/src/Components/Components/src/PlatformInfo.cs +++ b/src/Components/Components/src/PlatformInfo.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Components static PlatformInfo() { - IsWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")); + IsWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Browser); } } } diff --git a/src/Components/Components/src/Profiling/ComponentsProfiling.cs b/src/Components/Components/src/Profiling/ComponentsProfiling.cs new file mode 100644 index 0000000000..f47a0c917c --- /dev/null +++ b/src/Components/Components/src/Profiling/ComponentsProfiling.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Components.Profiling +{ + internal abstract class ComponentsProfiling + { + // For now, this is only intended for use on Blazor WebAssembly, and will have no effect + // when running on Blazor Server. The reason for having the ComponentsProfiling abstraction + // is so that if we later have two different implementations (one for WebAssembly, one for + // Server), the execution characteristics of calling Start/End will be unchanged and historical + // perf data will still be comparable to newer data. + public static readonly ComponentsProfiling Instance = PlatformInfo.IsWebAssembly + ? new WebAssemblyComponentsProfiling() + : (ComponentsProfiling)new NoOpComponentsProfiling(); + + public abstract void Start([CallerMemberName] string? name = null); + public abstract void End([CallerMemberName] string? name = null); + } +} diff --git a/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs b/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs new file mode 100644 index 0000000000..74037e7de7 --- /dev/null +++ b/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs @@ -0,0 +1,16 @@ +// 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.Components.Profiling +{ + internal class NoOpComponentsProfiling : ComponentsProfiling + { + public override void Start(string? name) + { + } + + public override void End(string? name) + { + } + } +} diff --git a/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs b/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs new file mode 100644 index 0000000000..4e0b6dbd74 --- /dev/null +++ b/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs @@ -0,0 +1,41 @@ +// 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 WebAssembly.JSInterop; + +namespace Microsoft.AspNetCore.Components.Profiling +{ + // Later on, we will likely want to move this into the WebAssembly package. However it needs to + // be inlined into the Components package directly until we're ready to make the underlying + // ComponentsProfile abstraction into a public API. It's possible that this API will never become + // public, or that it will be replaced by something more standard for .NET, if it's possible to + // make that work performantly on WebAssembly. + + internal class WebAssemblyComponentsProfiling : ComponentsProfiling + { + static bool IsCapturing = false; + + public static void SetCapturing(bool isCapturing) + { + IsCapturing = isCapturing; + } + + public override void Start(string? name) + { + if (IsCapturing) + { + InternalCalls.InvokeJSUnmarshalled( + out _, "_blazorProfileStart", name, null, null); + } + } + + public override void End(string? name) + { + if (IsCapturing) + { + InternalCalls.InvokeJSUnmarshalled( + out _, "_blazorProfileEnd", name, null, null); + } + } + } +} diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index 7ef9f94554..a7892991f4 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -5,6 +5,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.RenderTree @@ -25,6 +28,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree ArrayRange oldTree, ArrayRange newTree) { + ComponentsProfiling.Instance.Start(); var editsBuffer = batchBuilder.EditsBuffer; var editsBufferStartLength = editsBuffer.Count; @@ -32,7 +36,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree AppendDiffEntriesForRange(ref diffContext, 0, oldTree.Count, 0, newTree.Count); var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count); - return new RenderTreeDiff(componentId, editsSegment); + var result = new RenderTreeDiff(componentId, editsSegment); + ComponentsProfiling.Instance.End(); + return result; } public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange frames) @@ -43,6 +49,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { + ProfilingStart(); // This is deliberately a very large method. Parts of it could be factored out // into other private methods, but doing so comes at a consequential perf cost, // because it involves so much parameter passing. You can think of the code here @@ -293,10 +300,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos); } } + ProfilingEnd(); } private static Dictionary BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { + ProfilingStart(); var result = diffContext.KeyedItemInfoDictionaryPool.Get(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; @@ -342,6 +351,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree newStartIndex = NextSiblingIndex(frame, newStartIndex); } + ProfilingEnd(); return result; } @@ -374,6 +384,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { + ProfilingStart(); // The overhead of the dictionary used by AppendAttributeDiffEntriesForRangeSlow is // significant, so we want to try and do a merge-join if possible, but fall back to // a hash-join if not. We'll do a merge join until we hit a case we can't handle and @@ -422,6 +433,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree ref diffContext, oldStartIndex, oldEndIndexExcl, newStartIndex, newEndIndexExcl); + ProfilingEnd(); return; } @@ -447,9 +459,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree ref diffContext, oldStartIndex, oldEndIndexExcl, newStartIndex, newEndIndexExcl); + ProfilingEnd(); return; } } + + ProfilingEnd(); } private static void AppendAttributeDiffEntriesForRangeSlow( @@ -457,6 +472,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { + ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; @@ -495,6 +511,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree // We should have processed any additions at this point. Reset for the next batch. diffContext.AttributeDiffSet.Clear(); + ProfilingEnd(); } private static void UpdateRetainedChildComponent( @@ -502,6 +519,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldComponentIndex, int newComponentIndex) { + ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldComponentFrame = ref oldTree[oldComponentIndex]; @@ -528,6 +546,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree { componentState.SetDirectParameters(newParameters); } + + ProfilingEnd(); } private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex) @@ -550,6 +570,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldFrameIndex, int newFrameIndex) { + ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldFrame = ref oldTree[oldFrameIndex]; @@ -562,6 +583,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { InsertNewFrame(ref diffContext, newFrameIndex); RemoveOldFrame(ref diffContext, oldFrameIndex); + ProfilingEnd(); return; } @@ -687,6 +709,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}"); } + + ProfilingEnd(); } // This should only be called for attributes that have the same name. This is an @@ -696,6 +720,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldFrameIndex, int newFrameIndex) { + ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldFrame = ref oldTree[oldFrameIndex]; @@ -724,10 +749,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree // since it was unchanged. newFrame = oldFrame; } + + ProfilingEnd(); } private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex) { + ProfilingStart(); var newTree = diffContext.NewTree; ref var newFrame = ref newTree[newFrameIndex]; switch (newFrame.FrameType) @@ -780,10 +808,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}"); } + ProfilingEnd(); } private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex) { + ProfilingStart(); var oldTree = diffContext.OldTree; ref var oldFrame = ref oldTree[oldFrameIndex]; switch (oldFrame.FrameType) @@ -825,6 +855,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}"); } + ProfilingEnd(); } private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex) @@ -858,6 +889,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex) { + ProfilingStart(); var frames = diffContext.NewTree; var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength; for (var i = frameIndex; i < endIndexExcl; i++) @@ -879,10 +911,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree break; } } + ProfilingEnd(); } private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex) { + ProfilingStart(); var frames = diffContext.NewTree; ref var frame = ref frames[frameIndex]; @@ -899,6 +933,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex); childComponentState.SetDirectParameters(initialParameters); + ProfilingEnd(); } private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame) @@ -917,7 +952,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame) { - var newElementReference = ElementReference.CreateWithUniqueId(); + var newElementReference = ElementReference.CreateWithUniqueId(diffContext.Renderer.ElementReferenceContext); newFrame = newFrame.WithElementReferenceCaptureId(newElementReference.Id); newFrame.ElementReferenceCaptureAction(newElementReference); } @@ -943,6 +978,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl) { + ProfilingStart(); for (var i = startIndex; i < endIndexExcl; i++) { ref var frame = ref frames[i]; @@ -955,6 +991,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId); } } + ProfilingEnd(); } /// @@ -996,5 +1033,18 @@ namespace Microsoft.AspNetCore.Components.RenderTree SiblingIndex = 0; } } + + // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact + // even when capturing is disabled. So, to enable detailed profiling for this class, define the + // Profile_RenderTreeDiffBuilder compiler symbol, otherwise the calls are compiled out entirely. + // Enabling detailed profiling adds about 5% to rendering benchmark times. + + [Conditional("Profile_RenderTreeDiffBuilder")] + private static void ProfilingStart([CallerMemberName] string? name = null) + => ComponentsProfiling.Instance.Start(name); + + [Conditional("Profile_RenderTreeDiffBuilder")] + private static void ProfilingEnd([CallerMemberName] string? name = null) + => ComponentsProfiling.Instance.End(name); } } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 06e0602aa5..bb8bddcdc2 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.Logging; @@ -76,6 +77,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree /// public abstract Dispatcher Dispatcher { get; } + /// + /// Gets or sets the associated with this , + /// if it exists. + /// + protected internal ElementReferenceContext? ElementReferenceContext { get; protected set; } + /// /// Constructs a new component of the specified type. /// @@ -214,6 +221,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree /// public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) { + ComponentsProfiling.Instance.Start(); Dispatcher.AssertAccess(); if (!_eventBindings.TryGetValue(eventHandlerId, out var callback)) @@ -241,6 +249,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree catch (Exception e) { HandleException(e); + ComponentsProfiling.Instance.End(); return Task.CompletedTask; } finally @@ -254,7 +263,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree // Task completed synchronously or is still running. We already processed all of the rendering // work that was queued so let our error handler deal with it. - return GetErrorHandledTask(task); + var result = GetErrorHandledTask(task); + ComponentsProfiling.Instance.End(); + return result; } internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId) @@ -404,6 +415,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree private void ProcessRenderQueue() { + ComponentsProfiling.Instance.Start(); Dispatcher.AssertAccess(); if (_isBatchInProgress) @@ -418,6 +430,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { if (_batchBuilder.ComponentRenderQueue.Count == 0) { + ComponentsProfiling.Instance.End(); return; } @@ -429,7 +442,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree } var batch = _batchBuilder.ToBatch(); + ComponentsProfiling.Instance.Start(nameof(UpdateDisplayAsync)); updateDisplayTask = UpdateDisplayAsync(batch); + ComponentsProfiling.Instance.End(nameof(UpdateDisplayAsync)); // Fire off the execution of OnAfterRenderAsync, but don't wait for it // if there is async work to be done. @@ -439,6 +454,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { // Ensure we catch errors while running the render functions of the components. HandleException(e); + ComponentsProfiling.Instance.End(); return; } finally @@ -456,6 +472,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree { ProcessRenderQueue(); } + ComponentsProfiling.Instance.End(); } private Task InvokeRenderCompletedCalls(ArrayRange updatedComponents, Task updateDisplayTask) diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs index 3655d49335..7b755efd5d 100644 --- a/src/Components/Components/src/Rendering/ComponentState.cs +++ b/src/Components/Components/src/Rendering/ComponentState.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components.Rendering @@ -56,6 +57,7 @@ namespace Microsoft.AspNetCore.Components.Rendering public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment) { + ComponentsProfiling.Instance.Start(); // A component might be in the render queue already before getting disposed by an // earlier entry in the render queue. In that case, rendering is a no-op. if (_componentWasDisposed) @@ -67,7 +69,9 @@ namespace Microsoft.AspNetCore.Components.Rendering (CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree); CurrentRenderTree.Clear(); + ComponentsProfiling.Instance.Start("BuildRenderTree"); renderFragment(CurrentRenderTree); + ComponentsProfiling.Instance.End("BuildRenderTree"); var diff = RenderTreeDiffBuilder.ComputeDiff( _renderer, @@ -77,6 +81,7 @@ namespace Microsoft.AspNetCore.Components.Rendering CurrentRenderTree.GetFrames()); batchBuilder.UpdatedComponentDiffs.Append(diff); batchBuilder.InvalidateParameterViews(); + ComponentsProfiling.Instance.End(); } public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception) diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 2c73c2783a..224fc55ff9 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components.Rendering @@ -43,6 +45,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// A value representing the type of the element. public void OpenElement(int sequence, string elementName) { + ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -53,6 +56,7 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.Element(sequence, elementName)); + ProfilingEnd(); } /// @@ -61,6 +65,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void CloseElement() { + ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); // We might be closing an element with only attributes, run the duplicate cleanup pass @@ -72,6 +77,7 @@ namespace Microsoft.AspNetCore.Components.Rendering ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed); + ProfilingEnd(); } /// @@ -80,7 +86,11 @@ namespace Microsoft.AspNetCore.Components.Rendering /// An integer that represents the position of the instruction in the source code. /// Content for the new markup frame. public void AddMarkupContent(int sequence, string? markupContent) - => Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty)); + { + ProfilingStart(); + Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty)); + ProfilingEnd(); + } /// /// Appends a frame representing text content. @@ -88,7 +98,11 @@ namespace Microsoft.AspNetCore.Components.Rendering /// An integer that represents the position of the instruction in the source code. /// Content for the new text frame. public void AddContent(int sequence, string? textContent) - => Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty)); + { + ProfilingStart(); + Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty)); + ProfilingEnd(); + } /// /// Appends frames representing an arbitrary fragment of content. @@ -97,6 +111,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// Content to append. public void AddContent(int sequence, RenderFragment? fragment) { + ProfilingStart(); if (fragment != null) { // We surround the fragment with a region delimiter to indicate that the @@ -107,6 +122,7 @@ namespace Microsoft.AspNetCore.Components.Rendering fragment(this); CloseRegion(); } + ProfilingEnd(); } /// @@ -117,10 +133,12 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value used by . public void AddContent(int sequence, RenderFragment? fragment, TValue value) { + ProfilingStart(); if (fragment != null) { AddContent(sequence, fragment(value)); } + ProfilingEnd(); } /// @@ -153,6 +171,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value of the attribute. public void AddAttribute(int sequence, string name, bool value) { + ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -168,6 +187,7 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } + ProfilingEnd(); } /// @@ -184,6 +204,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value of the attribute. public void AddAttribute(int sequence, string name, string? value) { + ProfilingStart(); AssertCanAddAttribute(); if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -193,6 +214,7 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } + ProfilingEnd(); } /// @@ -209,6 +231,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value of the attribute. public void AddAttribute(int sequence, string name, MulticastDelegate? value) { + ProfilingStart(); AssertCanAddAttribute(); if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -218,6 +241,7 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } + ProfilingEnd(); } /// @@ -238,6 +262,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void AddAttribute(int sequence, string name, EventCallback value) { + ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -262,6 +287,7 @@ namespace Microsoft.AspNetCore.Components.Rendering // Track the attribute name if needed since we elided the frame. TrackAttributeName(name); } + ProfilingEnd(); } /// @@ -282,6 +308,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void AddAttribute(int sequence, string name, EventCallback value) { + ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -306,6 +333,7 @@ namespace Microsoft.AspNetCore.Components.Rendering // Track the attribute name if needed since we elided the frame. TrackAttributeName(name); } + ProfilingEnd(); } /// @@ -319,6 +347,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value of the attribute. public void AddAttribute(int sequence, string name, object? value) { + ProfilingStart(); // This looks a bit daunting because we need to handle the boxed/object version of all of the // types that AddAttribute special cases. if (_lastNonAttributeFrameType == RenderTreeFrameType.Element) @@ -371,6 +400,7 @@ namespace Microsoft.AspNetCore.Components.Rendering // This is going to throw. Calling it just to get a consistent exception message. AssertCanAddAttribute(); } + ProfilingEnd(); } /// @@ -385,6 +415,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// A holding the name and value of the attribute. public void AddAttribute(int sequence, in RenderTreeFrame frame) { + ProfilingStart(); if (frame.FrameType != RenderTreeFrameType.Attribute) { throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}."); @@ -392,6 +423,7 @@ namespace Microsoft.AspNetCore.Components.Rendering AssertCanAddAttribute(); Append(frame.WithAttributeSequence(sequence)); + ProfilingEnd(); } /// @@ -401,6 +433,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// A collection of key-value pairs representing attributes. public void AddMultipleAttributes(int sequence, IEnumerable>? attributes) { + ProfilingStart(); // Calling this up-front just to make sure we validate before mutating anything. AssertCanAddAttribute(); @@ -417,6 +450,7 @@ namespace Microsoft.AspNetCore.Components.Rendering AddAttribute(sequence, attribute.Key, attribute.Value); } } + ProfilingEnd(); } /// @@ -433,6 +467,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The name of another attribute whose value can be updated when the event handler is executed. public void SetUpdatesAttributeName(string updatesAttributeName) { + ProfilingStart(); if (_entries.Count == 0) { throw new InvalidOperationException("No preceding attribute frame exists."); @@ -445,6 +480,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName); + ProfilingEnd(); } /// @@ -476,10 +512,12 @@ namespace Microsoft.AspNetCore.Components.Rendering /// The value for the key. public void SetKey(object? value) { + ProfilingStart(); if (value == null) { // Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an // easy way to have conditional directive attributes + ProfilingEnd(); return; } @@ -502,10 +540,12 @@ namespace Microsoft.AspNetCore.Components.Rendering default: throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}."); } + ProfilingEnd(); } private void OpenComponentUnchecked(int sequence, Type componentType) { + ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -516,6 +556,7 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.ChildComponent(sequence, componentType)); + ProfilingEnd(); } /// @@ -524,6 +565,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void CloseComponent() { + ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); // We might be closing a component with only attributes. Run the attribute cleanup pass @@ -535,6 +577,7 @@ namespace Microsoft.AspNetCore.Components.Rendering ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed); + ProfilingEnd(); } /// @@ -544,12 +587,14 @@ namespace Microsoft.AspNetCore.Components.Rendering /// An action to be invoked whenever the reference value changes. public void AddElementReferenceCapture(int sequence, Action elementReferenceCaptureAction) { + ProfilingStart(); if (GetCurrentParentFrameType() != RenderTreeFrameType.Element) { throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}"); } Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction)); + ProfilingEnd(); } /// @@ -559,6 +604,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// An action to be invoked whenever the reference value changes. public void AddComponentReferenceCapture(int sequence, Action componentReferenceCaptureAction) { + ProfilingStart(); var parentFrameIndex = GetCurrentParentFrameIndex(); if (!parentFrameIndex.HasValue) { @@ -572,6 +618,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue)); + ProfilingEnd(); } /// @@ -580,6 +627,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// An integer that represents the position of the instruction in the source code. public void OpenRegion(int sequence) { + ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -590,6 +638,7 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.Region(sequence)); + ProfilingEnd(); } /// @@ -598,9 +647,11 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void CloseRegion() { + ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed); + ProfilingEnd(); } private void AssertCanAddAttribute() @@ -628,24 +679,29 @@ namespace Microsoft.AspNetCore.Components.Rendering /// public void Clear() { + ProfilingStart(); _entries.Clear(); _openElementIndices.Clear(); _lastNonAttributeFrameType = null; _hasSeenAddMultipleAttributes = false; _seenAttributeNames?.Clear(); + ProfilingEnd(); } // internal because this should only be used during the post-event tree patching logic // It's expensive because it involves copying all the subsequent memory in the array internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue) { + ProfilingStart(); // Replicate the same attribute omission logic as used elsewhere if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue)) { + ProfilingEnd(); return; } _entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue)); + ProfilingEnd(); } /// @@ -669,6 +725,7 @@ namespace Microsoft.AspNetCore.Components.Rendering // Internal for testing internal void ProcessDuplicateAttributes(int first) { + ProfilingStart(); Debug.Assert(_hasSeenAddMultipleAttributes); // When AddMultipleAttributes method has been called, we need to postprocess attributes while closing @@ -750,6 +807,7 @@ namespace Microsoft.AspNetCore.Components.Rendering seenAttributeNames.Clear(); _hasSeenAddMultipleAttributes = false; + ProfilingEnd(); } // Internal for testing @@ -766,7 +824,22 @@ namespace Microsoft.AspNetCore.Components.Rendering void IDisposable.Dispose() { + ProfilingStart(); _entries.Dispose(); + ProfilingEnd(); } + + // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact + // even when capturing is disabled. So, to enable detailed profiling for this class, define the + // Profile_RenderTreeBuilder compiler symbol, otherwise the calls are compiled out entirely. + // Enabling detailed profiling adds about 5% to rendering benchmark times. + + [Conditional("Profile_RenderTreeBuilder")] + private static void ProfilingStart([CallerMemberName] string? name = null) + => ComponentsProfiling.Instance.Start(name); + + [Conditional("Profile_RenderTreeBuilder")] + private static void ProfilingEnd([CallerMemberName] string? name = null) + => ComponentsProfiling.Instance.End(name); } } diff --git a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs index 77544baa77..b8908caff8 100644 --- a/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs +++ b/src/Components/Components/test/Rendering/RendererSynchronizationContextTest.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Components.Rendering Thread capturedThread = null; var e = new ManualResetEventSlim(); - + // Act context.Post((_) => { @@ -766,7 +766,6 @@ namespace Microsoft.AspNetCore.Components.Rendering } [Fact] - [QuarantinedTest] public async Task InvokeAsync_SyncWorkInAsyncTaskIsCompletedFirst() { // Simplified version of ServerComponentRenderingTest.CanDispatchAsyncWorkToSyncContext diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf index 0484ef907c..a73ab83b19 100644 --- a/src/Components/ComponentsNoDeps.slnf +++ b/src/Components/ComponentsNoDeps.slnf @@ -24,7 +24,6 @@ "WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "WebAssembly\\WebAssembly.Authentication\\src\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", - "WebAssembly\\WebAssemblyHttpHandler\\src\\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj", "WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj", diff --git a/src/Components/Directory.Build.targets b/src/Components/Directory.Build.targets index 1ecdb38eaf..69204d61a6 100644 --- a/src/Components/Directory.Build.targets +++ b/src/Components/Directory.Build.targets @@ -21,26 +21,4 @@ - - - - - - - - - <_HttpHandlerAssembly Include="@(_BlazorUserRuntimeAssembly)" - Condition="%(_BlazorUserRuntimeAssembly.ProjectReferenceOriginalItemSpec) == '$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj'" /> - - <_BlazorUserRuntimeAssembly Remove="@(_HttpHandlerAssembly)" /> - - diff --git a/src/Components/Server/src/Circuits/CircuitFactory.cs b/src/Components/Server/src/Circuits/CircuitFactory.cs index 4c74631535..73ccc4a536 100644 --- a/src/Components/Server/src/Circuits/CircuitFactory.cs +++ b/src/Components/Server/src/Circuits/CircuitFactory.cs @@ -64,7 +64,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _loggerFactory, _options, client, - _loggerFactory.CreateLogger()); + _loggerFactory.CreateLogger(), + jsRuntime.ElementReferenceContext); var circuitHandlers = scope.ServiceProvider.GetServices() .OrderBy(h => h.Order) diff --git a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs index 1d9205e1ba..41f03eab0f 100644 --- a/src/Components/Server/src/Circuits/RemoteJSRuntime.cs +++ b/src/Components/Server/src/Circuits/RemoteJSRuntime.cs @@ -17,12 +17,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private readonly ILogger _logger; private CircuitClientProxy _clientProxy; + public ElementReferenceContext ElementReferenceContext { get; } + public RemoteJSRuntime(IOptions options, ILogger logger) { _options = options.Value; _logger = logger; DefaultAsyncTimeout = _options.JSInteropDefaultCallTimeout; - JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter()); + ElementReferenceContext = new WebElementReferenceContext(this); + JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter(ElementReferenceContext)); } internal void Initialize(CircuitClientProxy clientProxy) diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index e7ab9c1a4c..0d9f862da2 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -37,12 +37,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, - ILogger logger) + ILogger logger, + ElementReferenceContext? elementReferenceContext) : base(serviceProvider, loggerFactory) { _client = client; _options = options; _logger = logger; + + ElementReferenceContext = elementReferenceContext; } public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); diff --git a/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/Components/Server/test/Circuits/CircuitHostTest.cs index 91a1ef26d7..e30c90d36f 100644 --- a/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -258,7 +258,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private class TestRemoteRenderer : RemoteRenderer { public TestRemoteRenderer(IServiceProvider serviceProvider, IClientProxy client) - : base(serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), new CircuitClientProxy(client, "connection"), NullLogger.Instance) + : base( + serviceProvider, + NullLoggerFactory.Instance, + new CircuitOptions(), + new CircuitClientProxy(client, "connection"), + NullLogger.Instance, + null) { } diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index 6befe0c65d..19f2ff0034 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -437,7 +437,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering private class TestRemoteRenderer : RemoteRenderer { public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, ILogger logger) - : base(serviceProvider, loggerFactory, options, client, logger) + : base(serviceProvider, loggerFactory, options, client, logger, null) { } diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index 1f3ea5450c..6fa1799496 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -38,7 +38,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits NullLoggerFactory.Instance, new CircuitOptions(), clientProxy, - NullLogger.Instance); + NullLogger.Instance, + null); } handlers = handlers ?? Array.Empty(); diff --git a/src/Components/Server/test/ElementReferenceJsonConverterTest.cs b/src/Components/Server/test/ElementReferenceJsonConverterTest.cs index 1fce318dd2..39449197b2 100644 --- a/src/Components/Server/test/ElementReferenceJsonConverterTest.cs +++ b/src/Components/Server/test/ElementReferenceJsonConverterTest.cs @@ -1,22 +1,31 @@ // 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.IO; using System.Text; using System.Text.Json; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Components { public class ElementReferenceJsonConverterTest { - private readonly ElementReferenceJsonConverter Converter = new ElementReferenceJsonConverter(); + private readonly ElementReferenceContext ElementReferenceContext; + private readonly ElementReferenceJsonConverter Converter; + + public ElementReferenceJsonConverterTest() + { + ElementReferenceContext = Mock.Of(); + Converter = new ElementReferenceJsonConverter(ElementReferenceContext); + } [Fact] public void Serializing_Works() { // Arrange - var elementReference = ElementReference.CreateWithUniqueId(); + var elementReference = ElementReference.CreateWithUniqueId(ElementReferenceContext); var expected = $"{{\"__internalId\":\"{elementReference.Id}\"}}"; var memoryStream = new MemoryStream(); var writer = new Utf8JsonWriter(memoryStream); @@ -34,7 +43,7 @@ namespace Microsoft.AspNetCore.Components public void Deserializing_Works() { // Arrange - var id = ElementReference.CreateWithUniqueId().Id; + var id = ElementReference.CreateWithUniqueId(ElementReferenceContext).Id; var json = $"{{\"__internalId\":\"{id}\"}}"; var bytes = Encoding.UTF8.GetBytes(json); var reader = new Utf8JsonReader(bytes); @@ -51,7 +60,7 @@ namespace Microsoft.AspNetCore.Components public void Deserializing_WithFormatting_Works() { // Arrange - var id = ElementReference.CreateWithUniqueId().Id; + var id = ElementReference.CreateWithUniqueId(ElementReferenceContext).Id; var json = @$"{{ ""__internalId"": ""{id}"" diff --git a/src/Components/Shared/src/ElementReferenceJsonConverter.cs b/src/Components/Shared/src/ElementReferenceJsonConverter.cs index 80a7738107..72143b2e72 100644 --- a/src/Components/Shared/src/ElementReferenceJsonConverter.cs +++ b/src/Components/Shared/src/ElementReferenceJsonConverter.cs @@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Components { private static readonly JsonEncodedText IdProperty = JsonEncodedText.Encode("__internalId"); + private readonly ElementReferenceContext _elementReferenceContext; + + public ElementReferenceJsonConverter(ElementReferenceContext elementReferenceContext) + { + _elementReferenceContext = elementReferenceContext; + } + public override ElementReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string id = null; @@ -39,7 +46,7 @@ namespace Microsoft.AspNetCore.Components throw new JsonException("__internalId is required."); } - return new ElementReference(id); + return new ElementReference(id, _elementReferenceContext); } public override void Write(Utf8JsonWriter writer, ElementReference value, JsonSerializerOptions options) diff --git a/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs b/src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs similarity index 88% rename from src/Components/WebAssembly/JSInterop/src/InternalCalls.cs rename to src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs index 60e19f025c..a28e45c410 100644 --- a/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs +++ b/src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace WebAssembly.JSInterop @@ -21,6 +22,6 @@ namespace WebAssembly.JSInterop public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson); [MethodImpl(MethodImplOptions.InternalCall)] - public static extern TRes InvokeJSUnmarshalled(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2); + public static extern TRes InvokeJSUnmarshalled(out string exception, string functionIdentifier, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2); } } diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 839e74a813..00f5db4495 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -1,19 +1,19 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=51)}([function(e,t,n){"use strict";var r;n.d(t,"a",(function(){return r})),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"}(r||(r={}))},function(e,t,n){"use strict";(function(e){n.d(t,"e",(function(){return c})),n.d(t,"a",(function(){return u})),n.d(t,"c",(function(){return l})),n.d(t,"g",(function(){return f})),n.d(t,"i",(function(){return h})),n.d(t,"j",(function(){return p})),n.d(t,"f",(function(){return d})),n.d(t,"d",(function(){return g})),n.d(t,"b",(function(){return y})),n.d(t,"h",(function(){return v}));var r=n(0),o=n(5),i=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]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch((function(e){}))},e}(),y=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 v(){var e="X-SignalR-User-Agent";return l.isNode&&(e="User-Agent"),[e,b(c,m(),E(),w())]}function b(e,t,n,r){var o="Microsoft SignalR/",i=e.split(".");return o+=i[0]+"."+i[1],o+=" ("+e+"; ",o+=t&&""!==t?t+"; ":"Unknown OS; ",o+=""+n,o+=r?"; "+r:"; Unknown Runtime Version",o+=")"}function m(){if(!l.isNode)return"";switch(e.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return e.platform}}function w(){if(l.isNode)return e.versions.node}function E(){return l.isNode?"NodeJS":"Browser"}}).call(this,n(13))},function(e,t,n){"use strict";n.r(t),n.d(t,"AbortError",(function(){return s})),n.d(t,"HttpError",(function(){return i})),n.d(t,"TimeoutError",(function(){return a})),n.d(t,"HttpClient",(function(){return l})),n.d(t,"HttpResponse",(function(){return u})),n.d(t,"DefaultHttpClient",(function(){return S})),n.d(t,"HubConnection",(function(){return O})),n.d(t,"HubConnectionState",(function(){return I})),n.d(t,"HubConnectionBuilder",(function(){return re})),n.d(t,"MessageType",(function(){return b})),n.d(t,"LogLevel",(function(){return f.a})),n.d(t,"HttpTransportType",(function(){return P})),n.d(t,"TransferFormat",(function(){return x})),n.d(t,"NullLogger",(function(){return $.a})),n.d(t,"JsonHubProtocol",(function(){return ee})),n.d(t,"Subject",(function(){return _})),n.d(t,"VERSION",(function(){return h.e}));var r,o=(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)}),i=function(e){function t(t,n){var r=this,o=this.constructor.prototype;return(r=e.call(this,t)||this).statusCode=n,r.__proto__=o,r}return o(t,e),t}(Error),a=function(e){function t(t){void 0===t&&(t="A timeout occurred.");var n=this,r=this.constructor.prototype;return(n=e.call(this,t)||this).__proto__=r,n}return o(t,e),t}(Error),s=function(e){function t(t){void 0===t&&(t="An abort occurred.");var n=this,r=this.constructor.prototype;return(n=e.call(this,t)||this).__proto__=r,n}return o(t,e),t}(Error),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&&o.status<300?n(new u(o.status,o.statusText,o.response||o.responseText)):r(new i(o.statusText,o.status))},o.onerror=function(){t.logger.log(f.a.Warning,"Error from HTTP request. "+o.status+": "+o.statusText+"."),r(new i(o.statusText,o.status))},o.ontimeout=function(){t.logger.log(f.a.Warning,"Timeout from HTTP request."),r(new a)},o.send(e.content||"")})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(l),E=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)}}(),S=function(e){function t(t){var n=e.call(this)||this;if("undefined"!=typeof fetch||h.c.isNode)n.httpClient=new v(t);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");n.httpClient=new w(t)}return n}return E(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new s):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}(l),C=n(42);!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"}(b||(b={}));var I,_=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 P.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new Y(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket,this.options.headers||{});case P.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new H(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource,this.options.withCredentials,this.options.headers||{});case P.LongPolling:return new B(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.withCredentials,this.options.headers||{});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=P[e.transport];if(null==r)return this.logger.log(f.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(f.a.Debug,"Skipping transport '"+P[r]+"' because it was disabled by the client."),new Error("'"+P[r]+"' is disabled by the client.");if(!(e.transferFormats.map((function(e){return x[e]})).indexOf(n)>=0))return this.logger.log(f.a.Debug,"Skipping transport '"+P[r]+"' because it does not support the requested transfer format '"+x[n]+"'."),new Error("'"+P[r]+"' does not support "+x[n]+".");if(r===P.WebSockets&&!this.options.WebSocket||r===P.ServerSentEvents&&!this.options.EventSource)return this.logger.log(f.a.Debug,"Skipping transport '"+P[r]+"' because it is not supported in your environment.'"),new Error("'"+P[r]+"' is not supported in your environment.");this.logger.log(f.a.Debug,"Selecting transport '"+P[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){var t=this;if(this.logger.log(f.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)throw this.logger.log(f.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is still in the connecting state."),new Error("HttpConnection.stopConnection("+e+") was called while the connection is still in the connecting state.");if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(f.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(f.a.Information,"Connection disconnected."),this.sendQueue&&(this.sendQueue.stop().catch((function(e){t.logger.log(f.a.Error,"TransportSendQueue.stop() threw error '"+e+"'.")})),this.sendQueue=void 0),this.connectionId=void 0,this.connectionState="Disconnected",this.connectionStarted){this.connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this.logger.log(f.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(f.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(!h.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(f.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",-1===(n+=-1===t?"":e.substring(t)).indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this.negotiateVersion),n},e}();var G=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new Q,this.transportResult=new Q,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new Q),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 K(this,void 0,void 0,(function(){var t,n,r;return V(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 Q,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]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]-1&&this.subject.observers.splice(e,1),0===this.subject.observers.length&&this.subject.cancelCallback&&this.subject.cancelCallback().catch((function(e){}))},e}(),y=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 v(){var e="X-SignalR-User-Agent";return l.isNode&&(e="User-Agent"),[e,b(c,m(),E(),w())]}function b(e,t,n,r){var o="Microsoft SignalR/",i=e.split(".");return o+=i[0]+"."+i[1],o+=" ("+e+"; ",o+=t&&""!==t?t+"; ":"Unknown OS; ",o+=""+n,o+=r?"; "+r:"; Unknown Runtime Version",o+=")"}function m(){if(!l.isNode)return"";switch(e.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return e.platform}}function w(){if(l.isNode)return e.versions.node}function E(){return l.isNode?"NodeJS":"Browser"}}).call(this,n(14))},function(e,t,n){"use strict";n.r(t),n.d(t,"AbortError",(function(){return s})),n.d(t,"HttpError",(function(){return i})),n.d(t,"TimeoutError",(function(){return a})),n.d(t,"HttpClient",(function(){return l})),n.d(t,"HttpResponse",(function(){return u})),n.d(t,"DefaultHttpClient",(function(){return S})),n.d(t,"HubConnection",(function(){return O})),n.d(t,"HubConnectionState",(function(){return I})),n.d(t,"HubConnectionBuilder",(function(){return re})),n.d(t,"MessageType",(function(){return b})),n.d(t,"LogLevel",(function(){return f.a})),n.d(t,"HttpTransportType",(function(){return P})),n.d(t,"TransferFormat",(function(){return x})),n.d(t,"NullLogger",(function(){return $.a})),n.d(t,"JsonHubProtocol",(function(){return ee})),n.d(t,"Subject",(function(){return _})),n.d(t,"VERSION",(function(){return h.e}));var r,o=(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)}),i=function(e){function t(t,n){var r=this,o=this.constructor.prototype;return(r=e.call(this,t)||this).statusCode=n,r.__proto__=o,r}return o(t,e),t}(Error),a=function(e){function t(t){void 0===t&&(t="A timeout occurred.");var n=this,r=this.constructor.prototype;return(n=e.call(this,t)||this).__proto__=r,n}return o(t,e),t}(Error),s=function(e){function t(t){void 0===t&&(t="An abort occurred.");var n=this,r=this.constructor.prototype;return(n=e.call(this,t)||this).__proto__=r,n}return o(t,e),t}(Error),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&&o.status<300?n(new u(o.status,o.statusText,o.response||o.responseText)):r(new i(o.statusText,o.status))},o.onerror=function(){t.logger.log(f.a.Warning,"Error from HTTP request. "+o.status+": "+o.statusText+"."),r(new i(o.statusText,o.status))},o.ontimeout=function(){t.logger.log(f.a.Warning,"Timeout from HTTP request."),r(new a)},o.send(e.content||"")})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))},t}(l),E=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)}}(),S=function(e){function t(t){var n=e.call(this)||this;if("undefined"!=typeof fetch||h.c.isNode)n.httpClient=new v(t);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");n.httpClient=new w(t)}return n}return E(t,e),t.prototype.send=function(e){return e.abortSignal&&e.abortSignal.aborted?Promise.reject(new s):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}(l),C=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"}(b||(b={}));var I,_=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 P.WebSockets:if(!this.options.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new Y(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.WebSocket,this.options.headers||{});case P.ServerSentEvents:if(!this.options.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new H(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.EventSource,this.options.withCredentials,this.options.headers||{});case P.LongPolling:return new B(this.httpClient,this.accessTokenFactory,this.logger,this.options.logMessageContent||!1,this.options.withCredentials,this.options.headers||{});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=P[e.transport];if(null==r)return this.logger.log(f.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(f.a.Debug,"Skipping transport '"+P[r]+"' because it was disabled by the client."),new Error("'"+P[r]+"' is disabled by the client.");if(!(e.transferFormats.map((function(e){return x[e]})).indexOf(n)>=0))return this.logger.log(f.a.Debug,"Skipping transport '"+P[r]+"' because it does not support the requested transfer format '"+x[n]+"'."),new Error("'"+P[r]+"' does not support "+x[n]+".");if(r===P.WebSockets&&!this.options.WebSocket||r===P.ServerSentEvents&&!this.options.EventSource)return this.logger.log(f.a.Debug,"Skipping transport '"+P[r]+"' because it is not supported in your environment.'"),new Error("'"+P[r]+"' is not supported in your environment.");this.logger.log(f.a.Debug,"Selecting transport '"+P[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){var t=this;if(this.logger.log(f.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)throw this.logger.log(f.a.Warning,"Call to HttpConnection.stopConnection("+e+") was ignored because the connection is still in the connecting state."),new Error("HttpConnection.stopConnection("+e+") was called while the connection is still in the connecting state.");if("Disconnecting"===this.connectionState&&this.stopPromiseResolver(),e?this.logger.log(f.a.Error,"Connection disconnected with error '"+e+"'."):this.logger.log(f.a.Information,"Connection disconnected."),this.sendQueue&&(this.sendQueue.stop().catch((function(e){t.logger.log(f.a.Error,"TransportSendQueue.stop() threw error '"+e+"'.")})),this.sendQueue=void 0),this.connectionId=void 0,this.connectionState="Disconnected",this.connectionStarted){this.connectionStarted=!1;try{this.onclose&&this.onclose(e)}catch(t){this.logger.log(f.a.Error,"HttpConnection.onclose("+e+") threw error '"+t+"'.")}}}else this.logger.log(f.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(!h.c.isBrowser||!window.document)throw new Error("Cannot resolve '"+e+"'.");var t=window.document.createElement("a");return t.href=e,this.logger.log(f.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",-1===(n+=-1===t?"":e.substring(t)).indexOf("negotiateVersion")&&(n+=-1===t?"?":"&",n+="negotiateVersion="+this.negotiateVersion),n},e}();var G=function(){function e(e){this.transport=e,this.buffer=[],this.executing=!0,this.sendBufferedData=new Q,this.transportResult=new Q,this.sendLoopPromise=this.sendLoop()}return e.prototype.send=function(e){return this.bufferData(e),this.transportResult||(this.transportResult=new Q),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 K(this,void 0,void 0,(function(){var t,n,r;return V(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 Q,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] * @license MIT */ -var r=n(52),o=n(53),i=n(54);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=!1;if((void 0===t||t<0)&&(t=0),t>this.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 x(this,t,n);case"utf8":case"utf-8":return k(this,t,n);case"ascii":return T(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function v(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:b(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):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(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 k(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<=4096)return String.fromCharCode.apply(String,e);var n="",r=0;for(;r0&&(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 m(this,e,t,n);case"utf8":case"utf-8":return w(this,e,t,n);case"ascii":return E(this,e,t,n);case"latin1":case"binary":return S(this,e,t,n);case"base64":return C(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(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)}};function T(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 L(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 D(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 j(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 A(e,t,n,r,i){return i||j(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,i){return i||j(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||R(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||R(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||R(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||R(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||R(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||R(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||R(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||R(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||R(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||R(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||R(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||R(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||R(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||R(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||R(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||R(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)||L(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||L(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||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||L(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||L(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);L(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);L(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||L(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||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||L(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||L(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 A(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return A(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return B(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(N,"")).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(8))},function(e,t,n){"use strict";var r=n(14).Buffer,o=n(55),i=n(20),a=n(68),s=n(71),c=n(72);e.exports=function(e){var t=[],n=[];return{encode:c(t,(e=e||{forceFloat64:!1,compatibilityMode:!1,disableTimestampEncoding:!1}).forceFloat64,e.compatibilityMode,e.disableTimestampEncoding),decode:s(n),register:function(e,t,n,a){return o(t,"must have a constructor"),o(n,"must have an encode function"),o(e>=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 r in e||(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){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var c,u=[],l=!1,f=-1;function h(){l&&c&&(l=!1,c.length?u=c.concat(u):f=-1,u.length&&p())}function p(){if(!l){var e=s(h);l=!0;for(var t=u.length;t;){for(c=u,u=[];++f1)for(var n=1;nthis.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)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=i,t.readUint64LE=function(e,t){var n=i(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+i(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<65535&&(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}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},function(e,t,n){(function(e){var r=Object.getOwnPropertyDescriptors||function(e){for(var t=Object.keys(e),n={},r=0;r=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="",I=!1,_=["{","}"];(p(n)&&(I=!0,_=["[","]"]),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||I&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=I?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,_)):_[0]+w+_[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")),P(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]"===I(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===I(e)}function S(e){return w(e)&&("[object Error]"===I(e)||e instanceof Error)}function C(e){return"function"==typeof e}function I(e){return Object.prototype.toString.call(e)}function _(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(57);var k=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function T(){var e=new Date,t=[_(e.getHours()),_(e.getMinutes()),_(e.getSeconds())].join(":");return[e.getDate(),k[e.getMonth()],t].join(" ")}function P(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){console.log("%s - %s",T(),t.format.apply(t,arguments))},t.inherits=n(58),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 O(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):_(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>=8388608?e=8388608:(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 C(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"),x(e)}function _(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 R(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 D(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?R(this):C(this),null;if(0===(e=S(e,t))&&t.ended)return 0===t.length&&R(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&&R(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,x(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!==D(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||(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 h(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=h.bind(r);return o.listener=n,r.wrapFn=o,o}function d(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n0&&(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=y(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 d(this,e,!0)},s.prototype.rawListeners=function(e){return d(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):g.call(e,t)},s.prototype.listenerCount=g,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(36).EventEmitter},function(e,t,n){"use strict";var r=n(21);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(64).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(21);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(19);u.inherits=n(15);var l={deprecate:n(67)},f=n(37),h=n(14).Buffer,p=o.Uint8Array||function(){};var d,g=n(38);function y(){}function v(e,t){s=s||n(9),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(I,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),I(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(9),!(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(),I(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"),I(e,t)}))}function I(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,I(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(13),n(65).setImmediate,n(8))},function(e,t,n){"use strict";e.exports=a;var r=n(9),o=n(19);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.lengths?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(10).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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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?a-4:a;for(n=0;n>16&255,c[l++]=t>>8&255,c[l++]=255&t;2===s&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,c[l++]=255&t);1===s&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,c[l++]=t>>8&255,c[l++]=255&t);return c},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){var r=n(56); +var r=n(54),o=n(55),i=n(56);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=!1;if((void 0===t||t<0)&&(t=0),t>this.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 x(this,t,n);case"utf8":case"utf-8":return k(this,t,n);case"ascii":return T(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return _(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function v(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:b(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):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(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 k(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<=4096)return String.fromCharCode.apply(String,e);var n="",r=0;for(;r0&&(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 m(this,e,t,n);case"utf8":case"utf-8":return w(this,e,t,n);case"ascii":return E(this,e,t,n);case"latin1":case"binary":return S(this,e,t,n);case"base64":return C(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(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)}};function T(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 L(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 D(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 j(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 A(e,t,n,r,i){return i||j(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,i){return i||j(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||R(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||R(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||R(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||R(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||R(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||R(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||R(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||R(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||R(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||R(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||R(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||R(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||R(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||R(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||R(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||R(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)||L(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||L(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||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||L(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||L(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);L(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);L(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||L(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||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||L(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||L(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 A(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return A(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return B(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(N,"")).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(9))},function(e,t,n){"use strict";var r=n(15).Buffer,o=n(57),i=n(21),a=n(70),s=n(73),c=n(74);e.exports=function(e){var t=[],n=[];return{encode:c(t,(e=e||{forceFloat64:!1,compatibilityMode:!1,disableTimestampEncoding:!1}).forceFloat64,e.compatibilityMode,e.disableTimestampEncoding),decode:s(n),register:function(e,t,n,a){return o(t,"must have a constructor"),o(n,"must have an encode function"),o(e>=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 r in e||(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){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var c,u=[],l=!1,f=-1;function h(){l&&c&&(l=!1,c.length?u=c.concat(u):f=-1,u.length&&p())}function p(){if(!l){var e=s(h);l=!0;for(var t=u.length;t;){for(c=u,u=[];++f1)for(var n=1;nthis.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)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=i,t.readUint64LE=function(e,t){var n=i(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+i(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<65535&&(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}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},function(e,t,n){(function(e){var r=Object.getOwnPropertyDescriptors||function(e){for(var t=Object.keys(e),n={},r=0;r=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="",I=!1,_=["{","}"];(p(n)&&(I=!0,_=["[","]"]),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||I&&0!=n.length?r<0?m(n)?e.stylize(RegExp.prototype.toString.call(n),"regexp"):e.stylize("[Object]","special"):(e.seen.push(n),u=I?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,_)):_[0]+w+_[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")),P(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]"===I(e)}function w(e){return"object"==typeof e&&null!==e}function E(e){return w(e)&&"[object Date]"===I(e)}function S(e){return w(e)&&("[object Error]"===I(e)||e instanceof Error)}function C(e){return"function"==typeof e}function I(e){return Object.prototype.toString.call(e)}function _(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(59);var k=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function T(){var e=new Date,t=[_(e.getHours()),_(e.getMinutes()),_(e.getSeconds())].join(":");return[e.getDate(),k[e.getMonth()],t].join(" ")}function P(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){console.log("%s - %s",T(),t.format.apply(t,arguments))},t.inherits=n(60),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 O(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):_(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>=8388608?e=8388608:(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 C(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"),x(e)}function _(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 R(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 D(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?R(this):C(this),null;if(0===(e=S(e,t))&&t.ended)return 0===t.length&&R(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&&R(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,x(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!==D(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||(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 h(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=h.bind(r);return o.listener=n,r.wrapFn=o,o}function d(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n0&&(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=y(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 d(this,e,!0)},s.prototype.rawListeners=function(e){return d(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):g.call(e,t)},s.prototype.listenerCount=g,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(38).EventEmitter},function(e,t,n){"use strict";var r=n(22);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(66).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(22);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(16);var l={deprecate:n(69)},f=n(39),h=n(15).Buffer,p=o.Uint8Array||function(){};var d,g=n(40);function y(){}function v(e,t){s=s||n(10),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(I,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),I(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(10),!(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(),I(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"),I(e,t)}))}function I(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,I(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(14),n(67).setImmediate,n(9))},function(e,t,n){"use strict";e.exports=a;var r=n(10),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.lengths?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(11).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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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?a-4:a;for(n=0;n>16&255,c[l++]=t>>8&255,c[l++]=255&t;2===s&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,c[l++]=255&t);1===s&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,c[l++]=t>>8&255,c[l++]=255&t);return c},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){var r=n(58); /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh * @license MIT - */function o(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(s=l[u],!m(e[s],t[s],n,r))return!1;return!0}(e,t,n,r))}return n?e===t:e==t}function w(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function E(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 S(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&&v(o,n,"Missing expected exception"+r);var i="string"==typeof r,s=!e&&o&&!n;if((!e&&a.isError(o)&&i&&E(o,n)||s)&&v(o,n,"Got unwanted exception"+r),e&&o&&n&&!E(o,n)||!e&&o)throw o}h.AssertionError=function(e){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=function(e){return g(y(e.actual),128)+" "+e.operator+" "+g(y(e.expected),128)}(this),this.generatedMessage=!0);var t=e.stackStartFunction||v;if(Error.captureStackTrace)Error.captureStackTrace(this,t);else{var n=new Error;if(n.stack){var r=n.stack,o=d(t),i=r.indexOf("\n"+o);if(i>=0){var a=r.indexOf("\n",i+1);r=r.substring(a+1)}this.stack=r}}},a.inherits(h.AssertionError,Error),h.fail=v,h.ok=b,h.equal=function(e,t,n){e!=t&&v(e,t,n,"==",h.equal)},h.notEqual=function(e,t,n){e==t&&v(e,t,n,"!=",h.notEqual)},h.deepEqual=function(e,t,n){m(e,t,!1)||v(e,t,n,"deepEqual",h.deepEqual)},h.deepStrictEqual=function(e,t,n){m(e,t,!0)||v(e,t,n,"deepStrictEqual",h.deepStrictEqual)},h.notDeepEqual=function(e,t,n){m(e,t,!1)&&v(e,t,n,"notDeepEqual",h.notDeepEqual)},h.notDeepStrictEqual=function e(t,n,r){m(t,n,!0)&&v(t,n,r,"notDeepStrictEqual",e)},h.strictEqual=function(e,t,n){e!==t&&v(e,t,n,"===",h.strictEqual)},h.notStrictEqual=function(e,t,n){e===t&&v(e,t,n,"!==",h.notStrictEqual)},h.throws=function(e,t,n){S(!0,e,t,n)},h.doesNotThrow=function(e,t,n){S(!1,e,t,n)},h.ifError=function(e){if(e)throw e},h.strict=r((function e(t,n){t||v(t,!0,n,"==",e)}),h,{equal:h.strictEqual,deepEqual:h.deepStrictEqual,notEqual:h.notStrictEqual,notDeepEqual:h.notDeepStrictEqual}),h.strict.strict=h.strict;var C=Object.keys||function(e){var t=[];for(var n in e)s.call(e,n)&&t.push(n);return t}}).call(this,n(8))},function(e,t,n){"use strict"; + */function o(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(s=l[u],!m(e[s],t[s],n,r))return!1;return!0}(e,t,n,r))}return n?e===t:e==t}function w(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function E(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 S(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&&v(o,n,"Missing expected exception"+r);var i="string"==typeof r,s=!e&&o&&!n;if((!e&&a.isError(o)&&i&&E(o,n)||s)&&v(o,n,"Got unwanted exception"+r),e&&o&&n&&!E(o,n)||!e&&o)throw o}h.AssertionError=function(e){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=function(e){return g(y(e.actual),128)+" "+e.operator+" "+g(y(e.expected),128)}(this),this.generatedMessage=!0);var t=e.stackStartFunction||v;if(Error.captureStackTrace)Error.captureStackTrace(this,t);else{var n=new Error;if(n.stack){var r=n.stack,o=d(t),i=r.indexOf("\n"+o);if(i>=0){var a=r.indexOf("\n",i+1);r=r.substring(a+1)}this.stack=r}}},a.inherits(h.AssertionError,Error),h.fail=v,h.ok=b,h.equal=function(e,t,n){e!=t&&v(e,t,n,"==",h.equal)},h.notEqual=function(e,t,n){e==t&&v(e,t,n,"!=",h.notEqual)},h.deepEqual=function(e,t,n){m(e,t,!1)||v(e,t,n,"deepEqual",h.deepEqual)},h.deepStrictEqual=function(e,t,n){m(e,t,!0)||v(e,t,n,"deepStrictEqual",h.deepStrictEqual)},h.notDeepEqual=function(e,t,n){m(e,t,!1)&&v(e,t,n,"notDeepEqual",h.notDeepEqual)},h.notDeepStrictEqual=function e(t,n,r){m(t,n,!0)&&v(t,n,r,"notDeepStrictEqual",e)},h.strictEqual=function(e,t,n){e!==t&&v(e,t,n,"===",h.strictEqual)},h.notStrictEqual=function(e,t,n){e===t&&v(e,t,n,"!==",h.notStrictEqual)},h.throws=function(e,t,n){S(!0,e,t,n)},h.doesNotThrow=function(e,t,n){S(!1,e,t,n)},h.ifError=function(e){if(e)throw e},h.strict=r((function e(t,n){t||v(t,!0,n,"==",e)}),h,{equal:h.strictEqual,deepEqual:h.deepStrictEqual,notEqual:h.notStrictEqual,notDeepEqual:h.notDeepStrictEqual}),h.strict.strict=h.strict;var C=Object.keys||function(e){var t=[];for(var n in e)s.call(e,n)&&t.push(n);return t}}).call(this,n(9))},function(e,t,n){"use strict"; /* object-assign (c) Sindre Sorhus @license MIT -*/var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,c=a(e),u=1;u0?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(10),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),a.prototype=Object.create(o.prototype),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(66),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(8))},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(35)).Stream=t,t.Readable=t,t.Writable=n(40),t.Duplex=n(9),t.Transform=n(41),t.PassThrough=n(70)},function(e,t,n){"use strict";e.exports=i;var r=n(41),o=n(19);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(20);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(34).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;a.1)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,a){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(!a&&"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,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 i(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 i(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 i(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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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(32),o=n(31),i=function(){function e(e){this.batchData=e;var t=new u(e);this.arrayRangeReader=new l(e),this.arrayBuilderSegmentReader=new f(e),this.diffReader=new a(e),this.editReader=new s(e,t),this.frameReader=new c(e,t)}return e.prototype.updatedComponents=function(){return o.readInt32LE(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return o.readInt32LE(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return o.readInt32LE(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return o.readInt32LE(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return o.readInt32LE(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 o.readInt32LE(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return o.readUint64LE(this.batchData,n)},e}();t.OutOfProcessRenderBatch=i;var a=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),s=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return o.readInt32LE(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=o.readInt32LE(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return o.readUint64LE(this.batchDataUint8,e+12)},e}(),u=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=o.readInt32LE(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t=o.readInt32LE(this.batchDataUint8,this.stringTableStartIndex+4*e),n=o.readLEB128(this.batchDataUint8,t),i=t+o.numLEB128Bytes(n),a=new Uint8Array(this.batchDataUint8.buffer,this.batchDataUint8.byteOffset+i,n);return r.decodeUtf8(a)},e}(),l=function(){function e(e){this.batchDataUint8=e}return e.prototype.count=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.values=function(e){return e+4},e}(),f=function(){function e(e){this.batchDataUint8=e}return e.prototype.offset=function(e){return 0},e.prototype.count=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.values=function(e){return e+4},e}()},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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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),n.d(t,"VERSION",(function(){return l})),n.d(t,"MessagePackHubProtocol",(function(){return u}));var r=n(10),o=n(11),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=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t,n=o(this.messagePackOptions);return t=e.streamIds?n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t,n=o(this.messagePackOptions);return t=e.streamIds?n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o(this.messagePackOptions).encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(this.messagePackOptions),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o(this.messagePackOptions).encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}(),l="5.0.0-dev"}]); \ No newline at end of file +*/var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,c=a(e),u=1;u0?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(11),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),a.prototype=Object.create(o.prototype),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(68),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(9))},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(37)).Stream=t,t.Readable=t,t.Writable=n(42),t.Duplex=n(10),t.Transform=n(43),t.PassThrough=n(72)},function(e,t,n){"use strict";e.exports=i;var r=n(43),o=n(20);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(16),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(21);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(36).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;a.1)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,a){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(!a&&"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,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 i(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 i(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 i(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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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(34),o=n(33),i=function(){function e(e){this.batchData=e;var t=new u(e);this.arrayRangeReader=new l(e),this.arrayBuilderSegmentReader=new f(e),this.diffReader=new a(e),this.editReader=new s(e,t),this.frameReader=new c(e,t)}return e.prototype.updatedComponents=function(){return o.readInt32LE(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return o.readInt32LE(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return o.readInt32LE(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return o.readInt32LE(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return o.readInt32LE(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 o.readInt32LE(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return o.readUint64LE(this.batchData,n)},e}();t.OutOfProcessRenderBatch=i;var a=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),s=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return o.readInt32LE(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return o.readInt32LE(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=o.readInt32LE(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=o.readInt32LE(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return o.readUint64LE(this.batchDataUint8,e+12)},e}(),u=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=o.readInt32LE(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t=o.readInt32LE(this.batchDataUint8,this.stringTableStartIndex+4*e),n=o.readLEB128(this.batchDataUint8,t),i=t+o.numLEB128Bytes(n),a=new Uint8Array(this.batchDataUint8.buffer,this.batchDataUint8.byteOffset+i,n);return r.decodeUtf8(a)},e}(),l=function(){function e(e){this.batchDataUint8=e}return e.prototype.count=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.values=function(e){return e+4},e}(),f=function(){function e(e){this.batchDataUint8=e}return e.prototype.offset=function(e){return 0},e.prototype.count=function(e){return o.readInt32LE(this.batchDataUint8,e)},e.prototype.values=function(e){return e+4},e}()},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),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){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).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=a.trys,(o=o.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),n.d(t,"VERSION",(function(){return l})),n.d(t,"MessagePackHubProtocol",(function(){return u}));var r=n(11),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=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=3?e[2]:void 0,error:e[1],type:i.MessageType.Close}},e.prototype.createPingMessage=function(e){if(e.length<1)throw new Error("Invalid payload for Ping message.");return{type:i.MessageType.Ping}},e.prototype.createInvocationMessage=function(e,t){if(t.length<5)throw new Error("Invalid payload for Invocation message.");var n=t[2];return n?{arguments:t[4],headers:e,invocationId:n,streamIds:[],target:t[3],type:i.MessageType.Invocation}:{arguments:t[4],headers:e,streamIds:[],target:t[3],type:i.MessageType.Invocation}},e.prototype.createStreamItemMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for StreamItem message.");return{headers:e,invocationId:t[2],item:t[3],type:i.MessageType.StreamItem}},e.prototype.createCompletionMessage=function(e,t){if(t.length<4)throw new Error("Invalid payload for Completion message.");var n,r,o=t[3];if(o!==this.voidResult&&t.length<5)throw new Error("Invalid payload for Completion message.");switch(o){case this.errorResult:n=t[4];break;case this.nonVoidResult:r=t[4]}return{error:n,headers:e,invocationId:t[2],result:r,type:i.MessageType.Completion}},e.prototype.writeInvocation=function(e){var t,n=o(this.messagePackOptions);return t=e.streamIds?n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.Invocation,e.headers||{},e.invocationId||null,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamInvocation=function(e){var t,n=o(this.messagePackOptions);return t=e.streamIds?n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments,e.streamIds]):n.encode([i.MessageType.StreamInvocation,e.headers||{},e.invocationId,e.target,e.arguments]),a.write(t.slice())},e.prototype.writeStreamItem=function(e){var t=o(this.messagePackOptions).encode([i.MessageType.StreamItem,e.headers||{},e.invocationId,e.item]);return a.write(t.slice())},e.prototype.writeCompletion=function(e){var t,n=o(this.messagePackOptions),r=e.error?this.errorResult:e.result?this.nonVoidResult:this.voidResult;switch(r){case this.errorResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.error]);break;case this.voidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r]);break;case this.nonVoidResult:t=n.encode([i.MessageType.Completion,e.headers||{},e.invocationId,r,e.result])}return a.write(t.slice())},e.prototype.writeCancelInvocation=function(e){var t=o(this.messagePackOptions).encode([i.MessageType.CancelInvocation,e.headers||{},e.invocationId]);return a.write(t.slice())},e.prototype.readHeaders=function(e){var t=e[1];if("object"!=typeof t)throw new Error("Invalid headers.");return t},e}(),l="5.0.0-dev"}]); \ No newline at end of file diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js index 59c0e5f467..dba09ec219 100644 --- a/src/Components/Web.JS/dist/Release/blazor.webassembly.js +++ b/src/Components/Web.JS/dist/Release/blazor.webassembly.js @@ -1 +1 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=43)}([,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){window.DotNet=e;var t=[],n={},r={},o=1,a=null;function i(e){t.push(e)}function u(e,t,n,r){var o=c();if(o.invokeDotNetFromJS){var a=JSON.stringify(r,m),i=o.invokeDotNetFromJS(e,t,n,a);return i?f(i):null}throw new Error("The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.")}function s(e,t,r,a){if(e&&r)throw new Error("For instance method calls, assemblyName should be null. Received '"+e+"'.");var i=o++,u=new Promise((function(e,t){n[i]={resolve:e,reject:t}}));try{var s=JSON.stringify(a,m);c().beginInvokeDotNetFromJS(i,e,t,r,s)}catch(e){l(i,!1,e)}return u}function c(){if(null!==a)return a;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 d(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){a=e},e.attachReviver=i,e.invokeMethod=function(e,t){for(var n=[],r=2;r0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function u(e,t,n){var a=e;if(e instanceof Comment&&(c(a)&&c(a).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(s(a))throw new Error("Not implemented: moving existing logical children");var i=c(t);if(n0;)e(r,0);var a=r;a.parentNode.removeChild(a)},t.getLogicalParent=s,t.getLogicalSiblingEnd=function(e){return e[a]||null},t.getLogicalChild=function(e,t){return c(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=c,t.permuteLogicalChildren=function(e,t){var n=c(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=s(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):d(r,e)})),t.forEach((function(e){for(var t=e.moveToBeforeMarker,n=t.parentNode,r=e.moveRangeStart,o=e.moveRangeEnd,a=r;a;){var i=a.nextSibling;if(n.insertBefore(a,t),a===o)break;a=i}n.removeChild(t)})),t.forEach((function(e){n[e.toSiblingIndex]=e.moveRangeStart}))},t.getClosestDomElement=l},,,,,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,n){"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.dispatchEvent=function(e,t){if(!r)throw new Error("eventDispatcher not initialized. Call 'setEventDispatcher' to configure it.");return r(e,t)},t.setEventDispatcher=function(e){r=e}},,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(7),o=n(4);window.Blazor={navigateTo:r.navigateTo,_internal:{attachRootComponentToElement:o.attachRootComponentToElement,navigationManager:r.internalFunctions}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(25),o=n(26),a=n(12),i=n(29),u=n(18),s=n(7),c=document.createElement("template"),l=document.createElementNS("http://www.w3.org/2000/svg","g"),f={submit:!0},d={},p=function(){function e(e){var t=this;this.childComponentLocations={},this.browserRendererId=e,this.eventDelegator=new o.EventDelegator((function(e,n,r,o){!function(e,t,n,r,o){f[e.type]&&e.preventDefault();var a={browserRendererId:t,eventHandlerId:n,eventArgsType:r.type,eventFieldInfo:o};u.dispatchEvent(a,r.data)}(e,t.browserRendererId,n,r,o)})),s.attachToEventDelegator(this.eventDelegator)}return e.prototype.attachRootComponentToLogicalElement=function(e,t){this.attachComponentToElement(e,t),d[e]=t},e.prototype.updateComponent=function(e,t,n,r){var o=this.childComponentLocations[t];if(!o)throw new Error("No element is currently associated with component "+t);var i=d[t];if(i){var u=a.getLogicalSiblingEnd(i);delete d[t],u?function(e,t){var n=a.getLogicalParent(e);if(!n)throw new Error("Can't clear between nodes. The start node does not have a logical parent.");for(var r=a.getLogicalChildrenArray(n),o=r.indexOf(e)+1,i=r.indexOf(t),u=o;u<=i;u++)a.removeLogicalChild(n,o);e.textContent="!"}(i,u):function(e){var t;for(;t=e.firstChild;)e.removeChild(t)}(i)}var s=a.getClosestDomElement(o).ownerDocument,c=s&&s.activeElement;this.applyEdits(e,t,o,0,n,r),c instanceof HTMLElement&&s&&s.activeElement!==c&&c.focus()},e.prototype.disposeComponent=function(e){delete this.childComponentLocations[e]},e.prototype.disposeEventHandler=function(e){this.eventDelegator.removeListener(e)},e.prototype.attachComponentToElement=function(e,t){this.childComponentLocations[e]=t},e.prototype.applyEdits=function(e,t,n,o,i,u){for(var s,c=0,l=o,f=e.arrayBuilderSegmentReader,d=e.editReader,p=e.frameReader,h=f.values(i),m=f.offset(i),v=m+f.count(i),y=m;y0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]>>0)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=a,t.readUint64LE=function(e,t){var n=a(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+a(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var a=e[t+o];if(n|=(127&a)<65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c)}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}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},,,,,,,,,,function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,a){function i(e){try{s(r.next(e))}catch(e){a(e)}}function u(e){try{s(r.throw(e))}catch(e){a(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function u(a){return function(u){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,r=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0)&&!(r=a.next()).done;)i.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=a.return)&&n.call(a)}finally{if(o)throw o.error}}return i};Object.defineProperty(t,"__esModule",{value:!0});var i=n(3);n(22);var u=n(17),s=n(44),c=n(4),l=n(47),f=n(33),d=n(18),p=n(48),h=n(49),m=n(50),v=!1;function y(e){return r(this,void 0,void 0,(function(){var t,n,f,y,b,g,w,E=this;return o(this,(function(_){switch(_.label){case 0:if(v)throw new Error("Blazor has already started.");return v=!0,d.setEventDispatcher((function(e,t){return i.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","DispatchEvent",e,JSON.stringify(t))})),t=u.setPlatform(s.monoPlatform),window.Blazor.platform=t,window.Blazor._internal.renderBatch=function(e,t){c.renderBatch(e,new l.SharedMemoryRenderBatch(t))},n=window.Blazor._internal.navigationManager.getBaseURI,f=window.Blazor._internal.navigationManager.getLocationHref,window.Blazor._internal.navigationManager.getUnmarshalledBaseURI=function(){return BINDING.js_string_to_mono_string(n())},window.Blazor._internal.navigationManager.getUnmarshalledLocationHref=function(){return BINDING.js_string_to_mono_string(f())},window.Blazor._internal.navigationManager.listenForNavigationEvents((function(e,t){return r(E,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,i.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t)];case 1:return n.sent(),[2]}}))}))})),[4,m.BootConfigResult.initAsync()];case 1:return y=_.sent(),[4,Promise.all([p.WebAssemblyResourceLoader.initAsync(y.bootConfig,e||{}),h.WebAssemblyConfigLoader.initAsync(y)])];case 2:b=a.apply(void 0,[_.sent(),1]),g=b[0],_.label=3;case 3:return _.trys.push([3,5,,6]),[4,t.start(g)];case 4:return _.sent(),[3,6];case 5:throw w=_.sent(),new Error("Failed to start platform. Reason: "+w);case 6:return t.callEntryPoint(g.bootConfig.entryAssembly),[2]}}))}))}window.Blazor.start=y,f.shouldAutoStart()&&y().catch((function(e){"undefined"!=typeof Module&&Module.printErr?Module.printErr(e):console.error(e)}))},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,a){function i(e){try{s(r.next(e))}catch(e){a(e)}}function u(e){try{s(r.throw(e))}catch(e){a(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function u(a){return function(u){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,r=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]>2]}t.monoPlatform={start:function(e){return new Promise((function(t,n){var l,f;u.attachDebuggerHotkey(e),window.Browser={init:function(){}},l=function(){window.Module=function(e,t,n){var l=this,f=e.bootConfig.resources,d=window.Module||{},h=["DEBUGGING ENABLED"];d.print=function(e){return h.indexOf(e)<0&&console.log(e)},d.printErr=function(e){console.error(e),s.showErrorNotification()},d.preRun=d.preRun||[],d.postRun=d.postRun||[],d.preloadPlugins=[];var y,b=e.loadResources(f.assembly,(function(e){return"_framework/"+e}),"assembly"),g=e.loadResources(f.pdb||{},(function(e){return"_framework/"+e}),"pdb"),w=e.loadResource("dotnet.wasm","_framework/dotnet.wasm",e.bootConfig.resources.runtime["dotnet.wasm"],"dotnetwasm");return e.bootConfig.resources.runtime.hasOwnProperty("dotnet.timezones.dat")&&(y=e.loadResource("dotnet.timezones.dat","_framework/dotnet.timezones.dat",e.bootConfig.resources.runtime["dotnet.timezones.dat"],"timezonedata")),d.instantiateWasm=function(e,t){return r(l,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,3,,4]),[4,w];case 1:return[4,v(o.sent(),e)];case 2:return n=o.sent(),[3,4];case 3:throw r=o.sent(),d.printErr(r),r;case 4:return t(n),[2]}}))})),[]},d.preRun.push((function(){a=cwrap("mono_wasm_add_assembly",null,["string","number","number"]),cwrap("mono_wasm_string_get_utf8","number",["number"]),MONO.loaded_files=[],y&&function(e){r(this,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return t="blazor:timezonedata",addRunDependency(t),[4,e.response];case 1:return[4,r.sent().arrayBuffer()];case 2:return n=r.sent(),c.loadTimezoneData(n),removeRunDependency(t),[2]}}))}))}(y),b.forEach((function(e){return E(e,function(e,t){var n=e.lastIndexOf(".");if(n<0)throw new Error("No extension to replace in '"+e+"'");return e.substr(0,n)+t}(e.name,".dll"))})),g.forEach((function(e){return E(e,e.name)})),window.Blazor._internal.dotNetCriticalError=function(e){d.printErr(BINDING.conv_string(e)||"(null)")},window.Blazor._internal.getSatelliteAssemblies=function(t){var n=BINDING.mono_array_to_js_array(t),a=e.bootConfig.resources.satelliteResources;if(a){var i=Promise.all(n.filter((function(e){return a.hasOwnProperty(e)})).map((function(t){return e.loadResources(a[t],(function(e){return"_framework/"+e}),"assembly")})).reduce((function(e,t){return e.concat(t)}),new Array).map((function(e){return r(l,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,e.response];case 1:return[2,t.sent().arrayBuffer()]}}))}))})));return BINDING.js_to_mono_obj(i.then((function(e){return e.length&&(window.Blazor._internal.readSatelliteAssemblies=function(){for(var t=BINDING.mono_obj_array_new(e.length),n=0;n>1];var n},readInt32Field:function(e,t){return d(e+(t||0))},readUint64Field:function(e,t){return function(e){var t=e>>2,n=Module.HEAPU32[t+1];if(n>f)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*l+Module.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),Module.HEAPF32[n>>2];var n},readObjectField:function(e,t){return d(e+(t||0))},readStringField:function(e,t,n){var r=d(e+(t||0));if(0===r)return null;if(n){var o=BINDING.unbox_mono_obj(r);return"boolean"==typeof o?o?"":null:o}return BINDING.conv_string(r)},readStructField:function(e,t){return e+(t||0)}};var p=document.createElement("a");function h(e){return e+12}function m(e,t,n){var r="["+e+"] "+t+":"+n;return BINDING.bind_static_method(r)}function v(e,t){return r(this,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:if("function"!=typeof WebAssembly.instantiateStreaming)return[3,4];o.label=1;case 1:return o.trys.push([1,3,,4]),[4,WebAssembly.instantiateStreaming(e.response,t)];case 2:return[2,o.sent().instance];case 3:return n=o.sent(),console.info("Streaming compilation failed. Falling back to ArrayBuffer instantiation. ",n),[3,4];case 4:return[4,e.response.then((function(e){return e.arrayBuffer()}))];case 5:return r=o.sent(),[4,WebAssembly.instantiate(r,t)];case 6:return[2,o.sent().instance]}}))}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=window.chrome&&navigator.userAgent.indexOf("Edge")<0,o=!1;function a(){return o&&r}t.hasDebuggingEnabled=a,t.attachDebuggerHotkey=function(e){o=!!e.bootConfig.resources.pdb;var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";a()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",(function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(o?r?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}},function(e,t,n){"use strict";var r=this&&this.__values||function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,a=n.call(e),i=[];try{for(;(void 0===t||t-- >0)&&!(r=a.next()).done;)i.push(r.value)}catch(e){o={error:e}}finally{try{r&&!r.done&&(n=a.return)&&n.call(a)}finally{if(o)throw o.error}}return i};Object.defineProperty(t,"__esModule",{value:!0});var a=n(31),i=n(32);t.loadTimezoneData=function(e){var t,n,u=new Uint8Array(e),s=a.readInt32LE(u,0);u=u.slice(4);var c=i.decodeUtf8(u.slice(0,s)),l=JSON.parse(c);u=u.slice(s),Module.FS_createPath("/","zoneinfo",!0,!0),new Set(l.map((function(e){return e[0].split("/")[0]}))).forEach((function(e){return Module.FS_createPath("/zoneinfo",e,!0,!0)}));try{for(var f=r(l),d=f.next();!d.done;d=f.next()){var p=o(d.value,2),h=p[0],m=p[1],v=u.slice(0,m);Module.FS_createDataFile("/zoneinfo/"+h,null,v,!0,!0,!0),u=u.slice(m)}}catch(e){t={error:e}}finally{try{d&&!d.done&&(n=f.return)&&n.call(f)}finally{if(t)throw t.error}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(17),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=a,this.arrayBuilderSegmentReader=i,this.diffReader=u,this.editReader=s,this.frameReader=c}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,a.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*a.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*a.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return l(e,t,u.structLength)},e.prototype.referenceFramesEntry=function(e,t){return l(e,t,c.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=l(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=l(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var a={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},i={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},u={structLength:4+i.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return l(e,t,s.structLength)}},s={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},c={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24,!0)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function l(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,a){function i(e){try{s(r.next(e))}catch(e){a(e)}}function u(e){try{s(r.throw(e))}catch(e){a(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,u)}s((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function u(a){return function(u){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,r=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[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&&!t)throw new Error("New logical elements must start empty, or allowExistingContents must be true");return r in e||(e[r]=[]),e}function u(e,t,n){var i=e;if(e instanceof Comment&&(c(i)&&c(i).length>0))throw new Error("Not implemented: inserting non-empty logical container");if(s(i))throw new Error("Not implemented: moving existing logical children");var a=c(t);if(n0;)e(r,0);var i=r;i.parentNode.removeChild(i)},t.getLogicalParent=s,t.getLogicalSiblingEnd=function(e){return e[i]||null},t.getLogicalChild=function(e,t){return c(e)[t]},t.isSvgElement=function(e){return"http://www.w3.org/2000/svg"===l(e).namespaceURI},t.getLogicalChildrenArray=c,t.permuteLogicalChildren=function(e,t){var n=c(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=s(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):d(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){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.setPlatform=function(e){return t.platform=e,t.platform}},function(e,t,n){"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.dispatchEvent=function(e,t){if(!r)throw new Error("eventDispatcher not initialized. Call 'setEventDispatcher' to configure it.");return r(e,t)},t.setEventDispatcher=function(e){r=e}},,,,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(8),o=n(4),i=n(31),a=n(5);window.Blazor={navigateTo:r.navigateTo,_internal:{attachRootComponentToElement:o.attachRootComponentToElement,navigationManager:r.internalFunctions,domWrapper:i.domFunctions,setProfilingEnabled:a.setProfilingEnabled}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(26),o=n(27),i=n(13),a=n(30),u=n(19),s=n(8),c=n(5),l=document.createElement("template"),f=document.createElementNS("http://www.w3.org/2000/svg","g"),d={submit:!0},p={},h=function(){function e(e){var t=this;this.childComponentLocations={},this.browserRendererId=e,this.eventDelegator=new o.EventDelegator((function(e,n,r,o){!function(e,t,n,r,o){d[e.type]&&e.preventDefault();var i={browserRendererId:t,eventHandlerId:n,eventArgsType:r.type,eventFieldInfo:o};u.dispatchEvent(i,r.data)}(e,t.browserRendererId,n,r,o)})),s.attachToEventDelegator(this.eventDelegator)}return e.prototype.attachRootComponentToLogicalElement=function(e,t){this.attachComponentToElement(e,t),p[e]=t},e.prototype.updateComponent=function(e,t,n,r){c.profileStart("updateComponent");var o=this.childComponentLocations[t];if(!o)throw new Error("No element is currently associated with component "+t);var a=p[t];if(a){var u=i.getLogicalSiblingEnd(a);delete p[t],u?function(e,t){var n=i.getLogicalParent(e);if(!n)throw new Error("Can't clear between nodes. The start node does not have a logical parent.");for(var r=i.getLogicalChildrenArray(n),o=r.indexOf(e)+1,a=r.indexOf(t),u=o;u<=a;u++)i.removeLogicalChild(n,o);e.textContent="!"}(a,u):function(e){var t;for(;t=e.firstChild;)e.removeChild(t)}(a)}var s=i.getClosestDomElement(o).ownerDocument,l=s&&s.activeElement;this.applyEdits(e,t,o,0,n,r),l instanceof HTMLElement&&s&&s.activeElement!==l&&l.focus(),c.profileEnd("updateComponent")},e.prototype.disposeComponent=function(e){delete this.childComponentLocations[e]},e.prototype.disposeEventHandler=function(e){this.eventDelegator.removeListener(e)},e.prototype.attachComponentToElement=function(e,t){this.childComponentLocations[e]=t},e.prototype.applyEdits=function(e,t,n,o,a,u){for(var s,c=0,l=o,f=e.arrayBuilderSegmentReader,d=e.editReader,p=e.frameReader,h=f.values(a),m=f.offset(a),v=m+f.count(a),y=m;y0&&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)}t.readInt32LE=function(e,t){return e[t]|e[t+1]<<8|e[t+2]<<16|e[t+3]<<24},t.readUint32LE=i,t.readUint64LE=function(e,t){var n=i(e,t+4);if(n>o)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*r+i(e,t)},t.readLEB128=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c)}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}),t.shouldAutoStart=function(){return!(!document||!document.currentScript||"false"===document.currentScript.getAttribute("autostart"))}},,,,,,,,,,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{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((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:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){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=a.trys,(o=o.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};Object.defineProperty(t,"__esModule",{value:!0});var a=n(3);n(23);var u=n(18),s=n(46),c=n(4),l=n(49),f=n(35),d=n(19),p=n(50),h=n(51),m=n(52),v=n(5),y=!1;function b(e){return r(this,void 0,void 0,(function(){var t,n,f,b,g,w,E,_=this;return o(this,(function(C){switch(C.label){case 0:if(y)throw new Error("Blazor has already started.");return y=!0,d.setEventDispatcher((function(e,t){return a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","DispatchEvent",e,JSON.stringify(t))})),t=u.setPlatform(s.monoPlatform),window.Blazor.platform=t,window.Blazor._internal.renderBatch=function(e,t){v.profileStart("renderBatch"),c.renderBatch(e,new l.SharedMemoryRenderBatch(t)),v.profileEnd("renderBatch")},n=window.Blazor._internal.navigationManager.getBaseURI,f=window.Blazor._internal.navigationManager.getLocationHref,window.Blazor._internal.navigationManager.getUnmarshalledBaseURI=function(){return BINDING.js_string_to_mono_string(n())},window.Blazor._internal.navigationManager.getUnmarshalledLocationHref=function(){return BINDING.js_string_to_mono_string(f())},window.Blazor._internal.navigationManager.listenForNavigationEvents((function(e,t){return r(_,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,a.DotNet.invokeMethodAsync("Microsoft.AspNetCore.Components.WebAssembly","NotifyLocationChanged",e,t)];case 1:return n.sent(),[2]}}))}))})),[4,m.BootConfigResult.initAsync()];case 1:return b=C.sent(),[4,Promise.all([p.WebAssemblyResourceLoader.initAsync(b.bootConfig,e||{}),h.WebAssemblyConfigLoader.initAsync(b)])];case 2:g=i.apply(void 0,[C.sent(),1]),w=g[0],C.label=3;case 3:return C.trys.push([3,5,,6]),[4,t.start(w)];case 4:return C.sent(),[3,6];case 5:throw E=C.sent(),new Error("Failed to start platform. Reason: "+E);case 6:return t.callEntryPoint(w.bootConfig.entryAssembly),[2]}}))}))}window.Blazor.start=b,f.shouldAutoStart()&&b().catch((function(e){"undefined"!=typeof Module&&Module.printErr?Module.printErr(e):console.error(e)}))},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{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((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:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){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=a.trys,(o=o.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]>2]}t.monoPlatform={start:function(e){return new Promise((function(t,n){var f,d;u.attachDebuggerHotkey(e),l.initializeProfiling((function(e){v("Microsoft.AspNetCore.Components","Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling","SetCapturing")(e)})),window.Browser={init:function(){}},f=function(){window.Module=function(e,t,n){var l=this,f=e.bootConfig.resources,d=window.Module||{},p=["DEBUGGING ENABLED"];d.print=function(e){return p.indexOf(e)<0&&console.log(e)},d.printErr=function(e){console.error(e),s.showErrorNotification()},d.preRun=d.preRun||[],d.postRun=d.postRun||[],d.preloadPlugins=[];var m,b=e.loadResources(f.assembly,(function(e){return"_framework/"+e}),"assembly"),g=e.loadResources(f.pdb||{},(function(e){return"_framework/"+e}),"pdb"),w=e.loadResource("dotnet.wasm","_framework/dotnet.wasm",e.bootConfig.resources.runtime["dotnet.wasm"],"dotnetwasm");return e.bootConfig.resources.runtime.hasOwnProperty("dotnet.timezones.dat")&&(m=e.loadResource("dotnet.timezones.dat","_framework/dotnet.timezones.dat",e.bootConfig.resources.runtime["dotnet.timezones.dat"],"timezonedata")),d.instantiateWasm=function(e,t){return r(l,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,3,,4]),[4,w];case 1:return[4,y(o.sent(),e)];case 2:return n=o.sent(),[3,4];case 3:throw r=o.sent(),d.printErr(r),r;case 4:return t(n),[2]}}))})),[]},d.preRun.push((function(){i=cwrap("mono_wasm_add_assembly",null,["string","number","number"]),cwrap("mono_wasm_string_get_utf8","number",["number"]),MONO.loaded_files=[],m&&function(e){r(this,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return t="blazor:timezonedata",addRunDependency(t),[4,e.response];case 1:return[4,r.sent().arrayBuffer()];case 2:return n=r.sent(),c.loadTimezoneData(n),removeRunDependency(t),[2]}}))}))}(m),b.forEach((function(e){return E(e,function(e,t){var n=e.lastIndexOf(".");if(n<0)throw new Error("No extension to replace in '"+e+"'");return e.substr(0,n)+t}(e.name,".dll"))})),g.forEach((function(e){return E(e,e.name)})),window.Blazor._internal.dotNetCriticalError=function(e){d.printErr(BINDING.conv_string(e)||"(null)")},window.Blazor._internal.getSatelliteAssemblies=function(t){var n=BINDING.mono_array_to_js_array(t),i=e.bootConfig.resources.satelliteResources;if(i){var a=Promise.all(n.filter((function(e){return i.hasOwnProperty(e)})).map((function(t){return e.loadResources(i[t],(function(e){return"_framework/"+e}),"assembly")})).reduce((function(e,t){return e.concat(t)}),new Array).map((function(e){return r(l,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,e.response];case 1:return[2,t.sent().arrayBuffer()]}}))}))})));return BINDING.js_to_mono_obj(a.then((function(e){return e.length&&(window.Blazor._internal.readSatelliteAssemblies=function(){for(var t=BINDING.mono_obj_array_new(e.length),n=0;n>1];var n},readInt32Field:function(e,t){return p(e+(t||0))},readUint64Field:function(e,t){return function(e){var t=e>>2,n=Module.HEAPU32[t+1];if(n>d)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*f+Module.HEAPU32[t]}(e+(t||0))},readFloatField:function(e,t){return n=e+(t||0),Module.HEAPF32[n>>2];var n},readObjectField:function(e,t){return p(e+(t||0))},readStringField:function(e,t,n){var r=p(e+(t||0));if(0===r)return null;if(n){var o=BINDING.unbox_mono_obj(r);return"boolean"==typeof o?o?"":null:o}return BINDING.conv_string(r)},readStructField:function(e,t){return e+(t||0)}};var h=document.createElement("a");function m(e){return e+12}function v(e,t,n){var r="["+e+"] "+t+":"+n;return BINDING.bind_static_method(r)}function y(e,t){return r(this,void 0,void 0,(function(){var n,r;return o(this,(function(o){switch(o.label){case 0:if("function"!=typeof WebAssembly.instantiateStreaming)return[3,4];o.label=1;case 1:return o.trys.push([1,3,,4]),[4,WebAssembly.instantiateStreaming(e.response,t)];case 2:return[2,o.sent().instance];case 3:return n=o.sent(),console.info("Streaming compilation failed. Falling back to ArrayBuffer instantiation. ",n),[3,4];case 4:return[4,e.response.then((function(e){return e.arrayBuffer()}))];case 5:return r=o.sent(),[4,WebAssembly.instantiate(r,t)];case 6:return[2,o.sent().instance]}}))}))}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=window.chrome&&navigator.userAgent.indexOf("Edge")<0,o=!1;function i(){return o&&r}t.hasDebuggingEnabled=i,t.attachDebuggerHotkey=function(e){o=!!e.bootConfig.resources.pdb;var t=navigator.platform.match(/^Mac/i)?"Cmd":"Alt";i()&&console.info("Debugging hotkey: Shift+"+t+"+D (when application has focus)"),document.addEventListener("keydown",(function(e){var t;e.shiftKey&&(e.metaKey||e.altKey)&&"KeyD"===e.code&&(o?r?((t=document.createElement("a")).href="_framework/debug?url="+encodeURIComponent(location.href),t.target="_blank",t.rel="noopener noreferrer",t.click()):console.error("Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging."):console.error("Cannot start debugging, because the application was not compiled with debugging enabled."))}))}},function(e,t,n){"use strict";var r=this&&this.__values||function(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >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};Object.defineProperty(t,"__esModule",{value:!0});var i=n(33),a=n(34);t.loadTimezoneData=function(e){var t,n,u=new Uint8Array(e),s=i.readInt32LE(u,0);u=u.slice(4);var c=a.decodeUtf8(u.slice(0,s)),l=JSON.parse(c);u=u.slice(s),Module.FS_createPath("/","zoneinfo",!0,!0),new Set(l.map((function(e){return e[0].split("/")[0]}))).forEach((function(e){return Module.FS_createPath("/zoneinfo",e,!0,!0)}));try{for(var f=r(l),d=f.next();!d.done;d=f.next()){var p=o(d.value,2),h=p[0],m=p[1],v=u.slice(0,m);Module.FS_createDataFile("/zoneinfo/"+h,null,v,!0,!0,!0),u=u.slice(m)}}catch(e){t={error:e}}finally{try{d&&!d.done&&(n=f.return)&&n.call(f)}finally{if(t)throw t.error}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(18),o=function(){function e(e){this.batchAddress=e,this.arrayRangeReader=i,this.arrayBuilderSegmentReader=a,this.diffReader=u,this.editReader=s,this.frameReader=c}return e.prototype.updatedComponents=function(){return r.platform.readStructField(this.batchAddress,0)},e.prototype.referenceFrames=function(){return r.platform.readStructField(this.batchAddress,i.structLength)},e.prototype.disposedComponentIds=function(){return r.platform.readStructField(this.batchAddress,2*i.structLength)},e.prototype.disposedEventHandlerIds=function(){return r.platform.readStructField(this.batchAddress,3*i.structLength)},e.prototype.updatedComponentsEntry=function(e,t){return l(e,t,u.structLength)},e.prototype.referenceFramesEntry=function(e,t){return l(e,t,c.structLength)},e.prototype.disposedComponentIdsEntry=function(e,t){var n=l(e,t,4);return r.platform.readInt32Field(n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=l(e,t,8);return r.platform.readUint64Field(n)},e}();t.SharedMemoryRenderBatch=o;var i={structLength:8,values:function(e){return r.platform.readObjectField(e,0)},count:function(e){return r.platform.readInt32Field(e,4)}},a={structLength:12,values:function(e){var t=r.platform.readObjectField(e,0),n=r.platform.getObjectFieldsBaseAddress(t);return r.platform.readObjectField(n,0)},offset:function(e){return r.platform.readInt32Field(e,4)},count:function(e){return r.platform.readInt32Field(e,8)}},u={structLength:4+a.structLength,componentId:function(e){return r.platform.readInt32Field(e,0)},edits:function(e){return r.platform.readStructField(e,4)},editsEntry:function(e,t){return l(e,t,s.structLength)}},s={structLength:20,editType:function(e){return r.platform.readInt32Field(e,0)},siblingIndex:function(e){return r.platform.readInt32Field(e,4)},newTreeIndex:function(e){return r.platform.readInt32Field(e,8)},moveToSiblingIndex:function(e){return r.platform.readInt32Field(e,8)},removedAttributeName:function(e){return r.platform.readStringField(e,16)}},c={structLength:36,frameType:function(e){return r.platform.readInt16Field(e,4)},subtreeLength:function(e){return r.platform.readInt32Field(e,8)},elementReferenceCaptureId:function(e){return r.platform.readStringField(e,16)},componentId:function(e){return r.platform.readInt32Field(e,12)},elementName:function(e){return r.platform.readStringField(e,16)},textContent:function(e){return r.platform.readStringField(e,16)},markupContent:function(e){return r.platform.readStringField(e,16)},attributeName:function(e){return r.platform.readStringField(e,16)},attributeValue:function(e){return r.platform.readStringField(e,24,!0)},attributeEventHandlerId:function(e){return r.platform.readUint64Field(e,8)}};function l(e,t,n){return r.platform.getArrayEntryPtr(e,t,n)}},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{s(r.next(e))}catch(e){i(e)}}function u(e){try{s(r.throw(e))}catch(e){i(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}s((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:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){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=a.trys,(o=o.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]): Promise { throw new Error('Blazor has already started.'); } started = true; + initializeProfiling(null); // Establish options to be used const options = resolveOptions(userOptions); diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts index 769bb43ab8..b2b432d855 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts @@ -11,6 +11,7 @@ import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader'; import { BootConfigResult } from './Platform/BootConfig'; import { Pointer } from './Platform/Platform'; import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions'; +import { profileStart, profileEnd } from './Platform/Profiling'; let started = false; @@ -27,7 +28,9 @@ async function boot(options?: Partial): Promise { const platform = Environment.setPlatform(monoPlatform); window['Blazor'].platform = platform; window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => { + profileStart('renderBatch'); renderBatch(browserRendererId, new SharedMemoryRenderBatch(batchAddress)); + profileEnd('renderBatch'); }; // Configure navigation via JS Interop diff --git a/src/Components/Web.JS/src/DomWrapper.ts b/src/Components/Web.JS/src/DomWrapper.ts new file mode 100644 index 0000000000..159aecd0a2 --- /dev/null +++ b/src/Components/Web.JS/src/DomWrapper.ts @@ -0,0 +1,13 @@ +import '@microsoft/dotnet-js-interop'; + +export const domFunctions = { + focus, +}; + +function focus(element: HTMLElement): void { + if (element instanceof HTMLElement) { + element.focus(); + } else { + throw new Error('Unable to focus an invalid element.'); + } +} diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 345e9b8920..08f7557ba5 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -1,5 +1,7 @@ import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager'; import { attachRootComponentToElement } from './Rendering/Renderer'; +import { domFunctions } from './DomWrapper'; +import { setProfilingEnabled } from './Platform/Profiling'; // Make the following APIs available in global scope for invocation from JS window['Blazor'] = { @@ -8,5 +10,7 @@ window['Blazor'] = { _internal: { attachRootComponentToElement, navigationManager: navigationManagerInternalFunctions, + domWrapper: domFunctions, + setProfilingEnabled: setProfilingEnabled, }, }; diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index ae1e93c6bd..da50b67e2e 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -5,6 +5,7 @@ import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResour import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform'; import { loadTimezoneData } from './TimezoneDataFile'; import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions'; +import { initializeProfiling } from '../Profiling'; let mono_string_get_utf8: (managedString: System_String) => Pointer; let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void; @@ -34,6 +35,10 @@ export const monoPlatform: Platform = { start: function start(resourceLoader: WebAssemblyResourceLoader) { return new Promise((resolve, reject) => { attachDebuggerHotkey(resourceLoader); + initializeProfiling(isCapturing => { + const setCapturingMethod = bindStaticMethod('Microsoft.AspNetCore.Components', 'Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling', 'SetCapturing'); + setCapturingMethod(isCapturing); + }); // dotnet.js assumes the existence of this window['Browser'] = { diff --git a/src/Components/Web.JS/src/Platform/Profiling.ts b/src/Components/Web.JS/src/Platform/Profiling.ts new file mode 100644 index 0000000000..9c4f0f220b --- /dev/null +++ b/src/Components/Web.JS/src/Platform/Profiling.ts @@ -0,0 +1,134 @@ +import { System_String } from './Platform'; + +interface TimingEntry { + // To minimize overhead, don't even decode the strings that arrive from .NET. Assume they are compile-time constants + // and hence the memory address will be fixed, so we can just store the pointer value. + name: string | System_String; + type: 'start' | 'end'; + timestamp: number; +} + +interface TraceEvent { + // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview + name: string; + cat: string; // Category + ph: 'B' | 'E'; // Phase + ts: number; // Timestamp in microseconds + pid: number; // Process ID + tid: number; // Thread ID +} + +let updateCapturingStateInHost: (isCapturing: boolean) => void; +let captureStartTime = 0; +const blazorProfilingEnabledKey = 'blazorProfilingEnabled'; +const profilingEnabled = !!localStorage[blazorProfilingEnabledKey]; +const entryLog: TimingEntry[] = []; +const openRegionsStack: (string | System_String)[] = []; + +export function setProfilingEnabled(enabled: boolean) { + // We only wire up the hotkeys etc. if the following localStorage entry is present during startup + // This is to ensure we're not interfering with any other hotkeys that developers might want to + // use for different purposes, plus it gives us a single point where we can notify .NET code during + // startup about whether profiling should be enabled. + localStorage[blazorProfilingEnabledKey] = (enabled !== false); + location.reload(); +} + +export function initializeProfiling(setCapturingCallback: ((isCapturing: boolean) => void) | null) { + if (!profilingEnabled) { + return; + } + + updateCapturingStateInHost = setCapturingCallback || (() => {}); + + // Attach hotkey (alt/cmd)+shift+p + const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt'; + console.info(`Profiling hotkey: Shift+${altKeyName}+P (when application has focus)`); + document.addEventListener('keydown', evt => { + if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyP') { + toggleCaptureEnabled(); + } + }); +} + +export function profileStart(name: System_String | string) { + if (!captureStartTime) { + return; + } + + const startTime = performance.now(); + openRegionsStack.push(name); + entryLog.push({ name: name, type: 'start', timestamp: startTime }); +} + +export function profileEnd(name: System_String | string) { + if (!captureStartTime) { + return; + } + + const endTime = performance.now(); + const poppedRegionName = openRegionsStack.pop(); + if (!poppedRegionName) { + throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the stack was empty.`); + } else if (poppedRegionName !== name) { + throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the top stack item was '${readJsString(poppedRegionName)}'.`); + } + + entryLog.push({ name: name, type: 'end', timestamp: endTime }); +} + +function profileReset() { + openRegionsStack.length = 0; + entryLog.length = 0; + captureStartTime = 0; + updateCapturingStateInHost(false); +} + +function profileExport() { + const traceEvents: TraceEvent[] = entryLog.map(entry => ({ + name: readJsString(entry.name)!, + cat: 'PERF', + ph: entry.type === 'start' ? 'B': 'E', + ts: (entry.timestamp - captureStartTime) * 1000, + pid: 0, + tid: 0, + })); + const traceEventsJson = JSON.stringify(traceEvents); + const traceEventsBuffer = new TextEncoder().encode(traceEventsJson); + const anchorElement = document.createElement('a'); + anchorElement.href = URL.createObjectURL(new Blob([traceEventsBuffer])); + anchorElement.setAttribute('download', 'trace.json'); + anchorElement.click(); + URL.revokeObjectURL(anchorElement.href); +} + +function toggleCaptureEnabled() { + if (!captureStartTime) { + displayOverlayMessage('Started capturing performance profile...'); + updateCapturingStateInHost(true); + captureStartTime = performance.now(); + } else { + displayOverlayMessage('Finished capturing performance profile'); + profileExport(); + profileReset(); + } +} + +function displayOverlayMessage(message: string) { + const elem = document.createElement('div'); + elem.textContent = message; + elem.setAttribute('style', 'position: absolute; z-index: 99999; font-family: \'Sans Serif\'; top: 0; left: 0; padding: 4px; font-size: 12px; background-color: purple; color: white;'); + document.body.appendChild(elem); + setTimeout(() => document.body.removeChild(elem), 3000); +} + +function readJsString(str: string | System_String) { + // This is expensive, so don't do it while capturing timings. Only do it as part of the export process. + return typeof str === 'string' ? str : BINDING.conv_string(str); +} + +// These globals deliberately differ from our normal conventions for attaching functions inside Blazor.* +// because the intention is to minimize overhead in all reasonable ways. Having any dot-separators in the +// name would cause extra string allocations on every invocation. +window['_blazorProfileStart'] = profileStart; +window['_blazorProfileEnd'] = profileEnd; diff --git a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts index 14faf70329..0c9c3ea2b9 100644 --- a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts +++ b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts @@ -6,6 +6,7 @@ import { applyCaptureIdToElement } from './ElementReferenceCapture'; import { EventFieldInfo } from './EventFieldInfo'; import { dispatchEvent } from './RendererEventDispatcher'; import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager'; +import { profileEnd, profileStart } from '../Platform/Profiling'; const selectValuePropname = '_blazorSelectValue'; const sharedTemplateElemForParsing = document.createElement('template'); const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g'); @@ -40,6 +41,8 @@ export class BrowserRenderer { } public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment, referenceFrames: ArrayValues): void { + profileStart('updateComponent'); + const element = this.childComponentLocations[componentId]; if (!element) { throw new Error(`No element is currently associated with component ${componentId}`); @@ -67,6 +70,8 @@ export class BrowserRenderer { if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) { activeElementBefore.focus(); } + + profileEnd('updateComponent'); } public disposeComponent(componentId: number) { diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs index 799d08e2f2..795dbe6ea3 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs @@ -14,6 +14,14 @@ namespace Microsoft.AspNetCore.Components public string? Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string? ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } + public static partial class ElementReferenceExtensions + { + public static System.Threading.Tasks.ValueTask FocusAsync(this Microsoft.AspNetCore.Components.ElementReference elementReference) { throw null; } + } + public partial class WebElementReferenceContext : Microsoft.AspNetCore.Components.ElementReferenceContext + { + public WebElementReferenceContext(Microsoft.JSInterop.IJSRuntime jsRuntime) { } + } } namespace Microsoft.AspNetCore.Components.Forms { @@ -53,7 +61,7 @@ namespace Microsoft.AspNetCore.Components.Forms protected TValue CurrentValue { get { throw null; } set { } } protected string? CurrentValueAsString { get { throw null; } set { } } protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] [System.Diagnostics.CodeAnalysis.AllowNullAttribute] @@ -63,7 +71,7 @@ namespace Microsoft.AspNetCore.Components.Forms [Microsoft.AspNetCore.Components.ParameterAttribute] public System.Linq.Expressions.Expression>? ValueExpression { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected virtual void Dispose(bool disposing) { } - protected virtual string? FormatValueAsString(TValue value) { throw null; } + protected virtual string? FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; } public override System.Threading.Tasks.Task SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) { throw null; } void System.IDisposable.Dispose() { } protected abstract bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage); @@ -80,7 +88,7 @@ namespace Microsoft.AspNetCore.Components.Forms [Microsoft.AspNetCore.Components.ParameterAttribute] public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } - protected override string FormatValueAsString(TValue value) { throw null; } + protected override string FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; } protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; } } public partial class InputNumber : Microsoft.AspNetCore.Components.Forms.InputBase @@ -89,9 +97,34 @@ namespace Microsoft.AspNetCore.Components.Forms [Microsoft.AspNetCore.Components.ParameterAttribute] public string ParsingErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } - protected override string? FormatValueAsString(TValue value) { throw null; } + protected override string? FormatValueAsString([System.Diagnostics.CodeAnalysis.AllowNullAttribute] TValue value) { throw null; } protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; } } + public partial class InputRadioGroup : Microsoft.AspNetCore.Components.Forms.InputBase + { + public InputRadioGroup() { } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void OnParametersSet() { } + protected override bool TryParseValueFromString(string? value, [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] out TValue result, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] out string? validationErrorMessage) { throw null; } + } + public partial class InputRadio : Microsoft.AspNetCore.Components.ComponentBase + { + public InputRadio() { } + [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] + public System.Collections.Generic.IReadOnlyDictionary? AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + [System.Diagnostics.CodeAnalysis.MaybeNullAttribute] + [System.Diagnostics.CodeAnalysis.AllowNullAttribute] + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void OnParametersSet() { } + } public partial class InputSelect : Microsoft.AspNetCore.Components.Forms.InputBase { public InputSelect() { } diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs index 287b9bfc57..e224abdafc 100644 --- a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs +++ b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs @@ -14,6 +14,14 @@ namespace Microsoft.AspNetCore.Components public string? Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string? ValueAttribute { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } + public static partial class ElementReferenceExtensions + { + public static System.Threading.Tasks.ValueTask FocusAsync(this Microsoft.AspNetCore.Components.ElementReference elementReference) { throw null; } + } + public partial class WebElementReferenceContext : Microsoft.AspNetCore.Components.ElementReferenceContext + { + public WebElementReferenceContext(Microsoft.JSInterop.IJSRuntime jsRuntime) { } + } } namespace Microsoft.AspNetCore.Components.Forms { @@ -51,7 +59,7 @@ namespace Microsoft.AspNetCore.Components.Forms protected TValue CurrentValue { get { throw null; } set { } } protected string? CurrentValueAsString { get { throw null; } set { } } protected Microsoft.AspNetCore.Components.Forms.EditContext EditContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - protected Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected internal Microsoft.AspNetCore.Components.Forms.FieldIdentifier FieldIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } [Microsoft.AspNetCore.Components.ParameterAttribute] @@ -88,6 +96,29 @@ namespace Microsoft.AspNetCore.Components.Forms protected override string? FormatValueAsString(TValue value) { throw null; } protected override bool TryParseValueFromString(string? value, out TValue result, out string? validationErrorMessage) { throw null; } } + public partial class InputRadioGroup : Microsoft.AspNetCore.Components.Forms.InputBase + { + public InputRadioGroup() { } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment? ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void OnParametersSet() { } + protected override bool TryParseValueFromString(string? value, out TValue result, out string? validationErrorMessage) { throw null; } + } + public partial class InputRadio : Microsoft.AspNetCore.Components.ComponentBase + { + public InputRadio() { } + [Microsoft.AspNetCore.Components.ParameterAttribute(CaptureUnmatchedValues=true)] + public System.Collections.Generic.IReadOnlyDictionary? AdditionalAttributes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void OnParametersSet() { } + } public partial class InputSelect : Microsoft.AspNetCore.Components.Forms.InputBase { public InputSelect() { } diff --git a/src/Components/Web/src/DomWrapperInterop.cs b/src/Components/Web/src/DomWrapperInterop.cs new file mode 100644 index 0000000000..db3085edb3 --- /dev/null +++ b/src/Components/Web/src/DomWrapperInterop.cs @@ -0,0 +1,12 @@ +// 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.Components +{ + internal static class DomWrapperInterop + { + private const string Prefix = "Blazor._internal.domWrapper."; + + public const string Focus = Prefix + "focus"; + } +} diff --git a/src/Components/Web/src/ElementReferenceExtensions.cs b/src/Components/Web/src/ElementReferenceExtensions.cs new file mode 100644 index 0000000000..67b4705503 --- /dev/null +++ b/src/Components/Web/src/ElementReferenceExtensions.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 System; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components +{ + public static class ElementReferenceExtensions + { + /// + /// Gives focus to an element given its . + /// + /// A reference to the element to focus. + /// The representing the asynchronous focus operation. + public static ValueTask FocusAsync(this ElementReference elementReference) + { + var jsRuntime = elementReference.GetJSRuntime(); + + if (jsRuntime == null) + { + throw new InvalidOperationException("No JavaScript runtime found."); + } + + return jsRuntime.InvokeVoidAsync(DomWrapperInterop.Focus, elementReference); + } + + internal static IJSRuntime GetJSRuntime(this ElementReference elementReference) + { + if (!(elementReference.Context is WebElementReferenceContext context)) + { + throw new InvalidOperationException("ElementReference has not been configured correctly."); + } + + return context.JSRuntime; + } + } +} diff --git a/src/Components/Web/src/Forms/InputBase.cs b/src/Components/Web/src/Forms/InputBase.cs index b414d3874c..5a8fceecf8 100644 --- a/src/Components/Web/src/Forms/InputBase.cs +++ b/src/Components/Web/src/Forms/InputBase.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Components.Forms /// /// Gets the for the bound value. /// - protected FieldIdentifier FieldIdentifier { get; set; } + protected internal FieldIdentifier FieldIdentifier { get; set; } /// /// Gets or sets the current value of the input. @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Components.Forms /// /// The value to format. /// A string representation of the value. - protected virtual string? FormatValueAsString(TValue value) + protected virtual string? FormatValueAsString([AllowNull] TValue value) => value?.ToString(); /// diff --git a/src/Components/Web/src/Forms/InputDate.cs b/src/Components/Web/src/Forms/InputDate.cs index 45583a849e..e7132988b9 100644 --- a/src/Components/Web/src/Forms/InputDate.cs +++ b/src/Components/Web/src/Forms/InputDate.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Forms } /// - protected override string FormatValueAsString(TValue value) + protected override string FormatValueAsString([AllowNull] TValue value) { switch (value) { diff --git a/src/Components/Web/src/Forms/InputExtensions.cs b/src/Components/Web/src/Forms/InputExtensions.cs new file mode 100644 index 0000000000..a1ace92141 --- /dev/null +++ b/src/Components/Web/src/Forms/InputExtensions.cs @@ -0,0 +1,35 @@ +// 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.CodeAnalysis; +using System.Globalization; + +namespace Microsoft.AspNetCore.Components.Forms +{ + internal static class InputExtensions + { + public static bool TryParseSelectableValueFromString(this InputBase input, string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage) + { + try + { + if (BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out var parsedValue)) + { + result = parsedValue; + validationErrorMessage = null; + return true; + } + else + { + result = default; + validationErrorMessage = $"The {input.FieldIdentifier.FieldName} field is not valid."; + return false; + } + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException($"{input.GetType()} does not support the type '{typeof(TValue)}'.", ex); + } + } + } +} diff --git a/src/Components/Web/src/Forms/InputNumber.cs b/src/Components/Web/src/Forms/InputNumber.cs index 9f3b75175e..515fc5ceac 100644 --- a/src/Components/Web/src/Forms/InputNumber.cs +++ b/src/Components/Web/src/Forms/InputNumber.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Forms /// /// The value to format. /// A string representation of the value. - protected override string? FormatValueAsString(TValue value) + protected override string? FormatValueAsString([AllowNull] TValue value) { // Avoiding a cast to IFormattable to avoid boxing. switch (value) diff --git a/src/Components/Web/src/Forms/InputRadio.cs b/src/Components/Web/src/Forms/InputRadio.cs new file mode 100644 index 0000000000..4a4ad46dc3 --- /dev/null +++ b/src/Components/Web/src/Forms/InputRadio.cs @@ -0,0 +1,82 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components.Rendering; + +namespace Microsoft.AspNetCore.Components.Forms +{ + /// + /// An input component used for selecting a value from a group of choices. + /// + public class InputRadio : ComponentBase + { + /// + /// Gets context for this . + /// + internal InputRadioContext? Context { get; private set; } + + /// + /// Gets or sets a collection of additional attributes that will be applied to the input element. + /// + [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary? AdditionalAttributes { get; set; } + + /// + /// Gets or sets the value of this input. + /// + [AllowNull] + [MaybeNull] + [Parameter] + public TValue Value { get; set; } = default; + + /// + /// Gets or sets the name of the parent input radio group. + /// + [Parameter] public string? Name { get; set; } + + [CascadingParameter] private InputRadioContext? CascadedContext { get; set; } + + private string GetCssClass(string fieldClass) + { + if (AdditionalAttributes != null && + AdditionalAttributes.TryGetValue("class", out var @class) && + !string.IsNullOrEmpty(Convert.ToString(@class))) + { + return $"{@class} {fieldClass}"; + } + + return fieldClass; + } + + /// + protected override void OnParametersSet() + { + Context = string.IsNullOrEmpty(Name) ? CascadedContext : CascadedContext?.FindContextInAncestors(Name); + + if (Context == null) + { + throw new InvalidOperationException($"{GetType()} must have an ancestor {typeof(InputRadioGroup)} " + + $"with a matching 'Name' property, if specified."); + } + } + + /// + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + Debug.Assert(Context != null); + + builder.OpenElement(0, "input"); + builder.AddMultipleAttributes(1, AdditionalAttributes); + builder.AddAttribute(2, "class", GetCssClass(Context.FieldClass)); + builder.AddAttribute(3, "type", "radio"); + builder.AddAttribute(4, "name", Context.GroupName); + builder.AddAttribute(5, "value", BindConverter.FormatValue(Value?.ToString())); + builder.AddAttribute(6, "checked", Context.CurrentValue?.Equals(Value)); + builder.AddAttribute(7, "onchange", Context.ChangeEventCallback); + builder.CloseElement(); + } + } +} diff --git a/src/Components/Web/src/Forms/InputRadioContext.cs b/src/Components/Web/src/Forms/InputRadioContext.cs new file mode 100644 index 0000000000..45f302871b --- /dev/null +++ b/src/Components/Web/src/Forms/InputRadioContext.cs @@ -0,0 +1,64 @@ +// 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.Components.Forms +{ + /// + /// Describes context for an component. + /// + internal class InputRadioContext + { + private readonly InputRadioContext? _parentContext; + + /// + /// Gets the name of the input radio group. + /// + public string GroupName { get; } + + /// + /// Gets the current selected value in the input radio group. + /// + public object? CurrentValue { get; } + + /// + /// Gets a css class indicating the validation state of input radio elements. + /// + public string FieldClass { get; } + + /// + /// Gets the event callback to be invoked when the selected value is changed. + /// + public EventCallback ChangeEventCallback { get; } + + /// + /// Instantiates a new . + /// + /// The parent . + /// The name of the input radio group. + /// The current selected value in the input radio group. + /// The css class indicating the validation state of input radio elements. + /// The event callback to be invoked when the selected value is changed. + public InputRadioContext( + InputRadioContext? parentContext, + string groupName, + object? currentValue, + string fieldClass, + EventCallback changeEventCallback) + { + _parentContext = parentContext; + + GroupName = groupName; + CurrentValue = currentValue; + FieldClass = fieldClass; + ChangeEventCallback = changeEventCallback; + } + + /// + /// Finds an in the context's ancestors with the matching . + /// + /// The group name of the ancestor . + /// The , or null if none was found. + public InputRadioContext? FindContextInAncestors(string groupName) + => string.Equals(GroupName, groupName) ? this : _parentContext?.FindContextInAncestors(groupName); + } +} diff --git a/src/Components/Web/src/Forms/InputRadioGroup.cs b/src/Components/Web/src/Forms/InputRadioGroup.cs new file mode 100644 index 0000000000..0e30fd483e --- /dev/null +++ b/src/Components/Web/src/Forms/InputRadioGroup.cs @@ -0,0 +1,57 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components.Rendering; + +namespace Microsoft.AspNetCore.Components.Forms +{ + /// + /// Groups child components. + /// + public class InputRadioGroup : InputBase + { + private readonly string _defaultGroupName = Guid.NewGuid().ToString("N"); + private InputRadioContext? _context; + + /// + /// Gets or sets the child content to be rendering inside the . + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// Gets or sets the name of the group. + /// + [Parameter] public string? Name { get; set; } + + [CascadingParameter] private InputRadioContext? CascadedContext { get; set; } + + /// + protected override void OnParametersSet() + { + var groupName = !string.IsNullOrEmpty(Name) ? Name : _defaultGroupName; + var fieldClass = EditContext.FieldCssClass(FieldIdentifier); + var changeEventCallback = EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString); + + _context = new InputRadioContext(CascadedContext, groupName, CurrentValue, fieldClass, changeEventCallback); + } + + /// + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + Debug.Assert(_context != null); + + builder.OpenComponent>(2); + builder.AddAttribute(3, "IsFixed", true); + builder.AddAttribute(4, "Value", _context); + builder.AddAttribute(5, "ChildContent", ChildContent); + builder.CloseComponent(); + } + + /// + protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage) + => this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage); + } +} diff --git a/src/Components/Web/src/Forms/InputSelect.cs b/src/Components/Web/src/Forms/InputSelect.cs index c5e6e54a94..b7d5dd7025 100644 --- a/src/Components/Web/src/Forms/InputSelect.cs +++ b/src/Components/Web/src/Forms/InputSelect.cs @@ -1,9 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.Forms @@ -13,8 +11,6 @@ namespace Microsoft.AspNetCore.Components.Forms /// public class InputSelect : InputBase { - private static readonly Type? _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue)); - /// /// Gets or sets the child content to be rendering inside the select element. /// @@ -34,31 +30,6 @@ namespace Microsoft.AspNetCore.Components.Forms /// protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage) - { - if (typeof(TValue) == typeof(string)) - { - result = (TValue)(object?)value; - validationErrorMessage = null; - return true; - } - else if (typeof(TValue).IsEnum || (_nullableUnderlyingType != null && _nullableUnderlyingType.IsEnum)) - { - var success = BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out var parsedValue); - if (success) - { - result = parsedValue; - validationErrorMessage = null; - return true; - } - else - { - result = default; - validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid."; - return false; - } - } - - throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(TValue)}'."); - } + => this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage); } } diff --git a/src/Components/Web/src/Properties/AssemblyInfo.cs b/src/Components/Web/src/Properties/AssemblyInfo.cs index 2741560028..891ea5326c 100644 --- a/src/Components/Web/src/Properties/AssemblyInfo.cs +++ b/src/Components/Web/src/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Web.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Components/Web/src/WebElementReferenceContext.cs b/src/Components/Web/src/WebElementReferenceContext.cs new file mode 100644 index 0000000000..ec81d075f8 --- /dev/null +++ b/src/Components/Web/src/WebElementReferenceContext.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. + +using System; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components +{ + public class WebElementReferenceContext : ElementReferenceContext + { + internal IJSRuntime JSRuntime { get; } + + public WebElementReferenceContext(IJSRuntime jsRuntime) + { + JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + } + } +} diff --git a/src/Components/Web/test/Forms/InputRadioTest.cs b/src/Components/Web/test/Forms/InputRadioTest.cs new file mode 100644 index 0000000000..1447a16316 --- /dev/null +++ b/src/Components/Web/test/Forms/InputRadioTest.cs @@ -0,0 +1,138 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.Test.Helpers; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Forms +{ + public class InputRadioTest + { + [Fact] + public async Task ThrowsOnFirstRenderIfInputRadioHasNoGroup() + { + var model = new TestModel(); + var rootComponent = new TestInputRadioHostComponent + { + EditContext = new EditContext(model), + InnerContent = RadioButtonsWithoutGroup(null) + }; + + var ex = await Assert.ThrowsAsync(() => RenderAndGetTestInputComponentAsync(rootComponent)); + Assert.Contains($"must have an ancestor", ex.Message); + } + + [Fact] + public async Task GroupGeneratesNameGuidWhenInvalidNameSupplied() + { + var model = new TestModel(); + var rootComponent = new TestInputRadioHostComponent + { + EditContext = new EditContext(model), + InnerContent = RadioButtonsWithGroup(null, () => model.TestEnum) + }; + + var inputRadioComponents = await RenderAndGetTestInputComponentAsync(rootComponent); + + Assert.All(inputRadioComponents, inputRadio => Assert.True(Guid.TryParseExact(inputRadio.GroupName, "N", out _))); + } + + [Fact] + public async Task RadioInputContextExistsWhenValidNameSupplied() + { + var groupName = "group"; + var model = new TestModel(); + var rootComponent = new TestInputRadioHostComponent + { + EditContext = new EditContext(model), + InnerContent = RadioButtonsWithGroup(groupName, () => model.TestEnum) + }; + + var inputRadioComponents = await RenderAndGetTestInputComponentAsync(rootComponent); + + Assert.All(inputRadioComponents, inputRadio => Assert.Equal(groupName, inputRadio.GroupName)); + } + + private static RenderFragment RadioButtonsWithoutGroup(string name) => (builder) => + { + foreach (var selectedValue in (TestEnum[])Enum.GetValues(typeof(TestEnum))) + { + builder.OpenComponent(0); + builder.AddAttribute(1, "Name", name); + builder.AddAttribute(2, "Value", selectedValue); + builder.CloseComponent(); + } + }; + + private static RenderFragment RadioButtonsWithGroup(string name, Expression> valueExpression) => (builder) => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Name", name); + builder.AddAttribute(2, "ValueExpression", valueExpression); + builder.AddAttribute(2, "ChildContent", new RenderFragment((childBuilder) => + { + foreach (var value in (TestEnum[])Enum.GetValues(typeof(TestEnum))) + { + childBuilder.OpenComponent(0); + childBuilder.AddAttribute(1, "Value", value); + childBuilder.CloseComponent(); + } + })); + + builder.CloseComponent(); + }; + + private static IEnumerable FindInputRadioComponents(CapturedBatch batch) + => batch.ReferenceFrames + .Where(f => f.FrameType == RenderTreeFrameType.Component) + .Select(f => f.Component) + .OfType(); + + private static async Task> RenderAndGetTestInputComponentAsync(TestInputRadioHostComponent rootComponent) + { + var testRenderer = new TestRenderer(); + var componentId = testRenderer.AssignRootComponentId(rootComponent); + await testRenderer.RenderRootComponentAsync(componentId); + return FindInputRadioComponents(testRenderer.Batches.Single()); + } + + private enum TestEnum + { + One, + Two, + Three + } + + private class TestModel + { + public TestEnum TestEnum { get; set; } + } + + private class TestInputRadio : InputRadio + { + public string GroupName => Context.GroupName; + } + + private class TestInputRadioHostComponent : AutoRenderComponent + { + public EditContext EditContext { get; set; } + + public RenderFragment InnerContent { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Value", EditContext); + builder.AddAttribute(2, "ChildContent", InnerContent); + builder.CloseComponent(); + } + } + } +} diff --git a/src/Components/Web/test/Forms/InputSelectTest.cs b/src/Components/Web/test/Forms/InputSelectTest.cs index 7c49fe03be..65c3351e5e 100644 --- a/src/Components/Web/test/Forms/InputSelectTest.cs +++ b/src/Components/Web/test/Forms/InputSelectTest.cs @@ -90,6 +90,88 @@ namespace Microsoft.AspNetCore.Components.Forms Assert.Null(inputSelectComponent.CurrentValue); } + // See: https://github.com/dotnet/aspnetcore/issues/9939 + [Fact] + public async Task ParsesCurrentValueWhenUsingNotNullableGuid() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NotNullableGuid + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + var guid = Guid.NewGuid(); + inputSelectComponent.CurrentValueAsString = guid.ToString(); + + // Assert + Assert.Equal(guid, inputSelectComponent.CurrentValue); + } + + // See: https://github.com/dotnet/aspnetcore/issues/9939 + [Fact] + public async Task ParsesCurrentValueWhenUsingNullableGuid() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NullableGuid + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + var guid = Guid.NewGuid(); + inputSelectComponent.CurrentValueAsString = guid.ToString(); + + // Assert + Assert.Equal(guid, inputSelectComponent.CurrentValue); + } + + // See: https://github.com/dotnet/aspnetcore/pull/19562 + [Fact] + public async Task ParsesCurrentValueWhenUsingNotNullableInt() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NotNullableInt + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = "42"; + + // Assert + Assert.Equal(42, inputSelectComponent.CurrentValue); + } + + // See: https://github.com/dotnet/aspnetcore/pull/19562 + [Fact] + public async Task ParsesCurrentValueWhenUsingNullableInt() + { + // Arrange + var model = new TestModel(); + var rootComponent = new TestInputSelectHostComponent + { + EditContext = new EditContext(model), + ValueExpression = () => model.NullableInt + }; + var inputSelectComponent = await RenderAndGetTestInputComponentAsync(rootComponent); + + // Act + inputSelectComponent.CurrentValueAsString = "42"; + + // Assert + Assert.Equal(42, inputSelectComponent.CurrentValue); + } + private static TestInputSelect FindInputSelectComponent(CapturedBatch batch) => batch.ReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Component) @@ -117,6 +199,14 @@ namespace Microsoft.AspNetCore.Components.Forms public TestEnum NotNullableEnum { get; set; } public TestEnum? NullableEnum { get; set; } + + public Guid NotNullableGuid { get; set; } + + public Guid? NullableGuid { get; set; } + + public int NotNullableInt { get; set; } + + public int? NullableInt { get; set; } } class TestInputSelect : InputSelect diff --git a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj index 8b58eb02dd..64d0f52e10 100644 --- a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj +++ b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs index 54649ad129..61e44bf3bb 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs @@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication var parameters = ParameterView.FromDictionary(new Dictionary { [_action] = RemoteAuthenticationActions.LogInCallback, - [_onLogInSucceded] = new EventCallbackFactory().Create< RemoteAuthenticationState>( + [_onLogInSucceded] = new EventCallbackFactory().Create( remoteAuthenticator, (state) => loggingSucceededCalled = true), }); diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj index 3ca9477b3e..4dde2eaae6 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -23,11 +23,6 @@ Private="false" Condition="'$(BuildNodeJS)' != 'false' and '$(BuildingInsideVisualStudio)' != 'true'" /> - - - diff --git a/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs index d9e81acf15..cfef7ed0d7 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs @@ -34,6 +34,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering // The WebAssembly renderer registers and unregisters itself with the static registry _webAssemblyRendererId = RendererRegistry.Add(this); _logger = loggerFactory.CreateLogger(); + + ElementReferenceContext = DefaultWebAssemblyJSRuntime.Instance.ElementReferenceContext; } public override Dispatcher Dispatcher => NullDispatcher.Instance; diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index 3ee84bd412..a9820985eb 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -10,9 +10,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services { internal static readonly DefaultWebAssemblyJSRuntime Instance = new DefaultWebAssemblyJSRuntime(); + public ElementReferenceContext ElementReferenceContext { get; } + private DefaultWebAssemblyJSRuntime() { - JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter()); + ElementReferenceContext = new WebElementReferenceContext(this); + JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter(ElementReferenceContext)); } #pragma warning disable IDE0051 // Remove unused private members. Invoked via Mono's JS interop mechanism (invoke_method) diff --git a/src/Components/Blazor/Blazor/test/Rendering/RenderRegistryTest.cs b/src/Components/WebAssembly/WebAssembly/test/RenderRegistryTest.cs similarity index 73% rename from src/Components/Blazor/Blazor/test/Rendering/RenderRegistryTest.cs rename to src/Components/WebAssembly/WebAssembly/test/RenderRegistryTest.cs index 6f37bf34d4..d36d10f0da 100644 --- a/src/Components/Blazor/Blazor/test/Rendering/RenderRegistryTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/RenderRegistryTest.cs @@ -1,7 +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. + using System; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Rendering +namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering { public class RenderRegistryTest { diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj deleted file mode 100644 index d8417f63b3..0000000000 --- a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - $(MSBuildProjectName).nuspec - netstandard2.1 - $(MSBuildProjectName) - System.Net.Http.WebAssemblyHttpHandler - 0.2.2.0 - true - Open - - - - - - - diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs deleted file mode 100644 index 0d0dbc1f75..0000000000 --- a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Net.Http; - -namespace System.Net.Http -{ - public partial class WebAssemblyHttpHandler : System.Net.Http.HttpMessageHandler - { - public WebAssemblyHttpHandler() { } - protected override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } - } -} diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec deleted file mode 100644 index fd5ccf21a0..0000000000 --- a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec +++ /dev/null @@ -1,13 +0,0 @@ - - - - $CommonMetadataElements$ - - - - - - $CommonFileElements$ - - - diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj index 4994c3eb7d..0ff01b2ab1 100644 --- a/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj @@ -4,8 +4,6 @@ $(DefaultNetCoreTargetFramework) browser-wasm true - - true diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj index 16fddd3ddc..484aa06fce 100644 --- a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj @@ -4,8 +4,6 @@ $(DefaultNetCoreTargetFramework) browser-wasm true - - true diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs index b2ae0170f6..c3c3ae618f 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs @@ -40,13 +40,12 @@ namespace Wasm.Performance.Driver return 1; } - if (stressRunSeconds < 1) + if (stressRunSeconds < 0) { Console.Error.WriteLine("Stress run duration must be a positive integer."); return 1; } - - if (stressRunSeconds > 0) + else if (stressRunSeconds > 0) { isStressRun = true; diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs index da5fbe4e1a..f9d7ef47ef 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs @@ -15,7 +15,13 @@ namespace Wasm.Performance.Driver { const int SeleniumPort = 4444; static bool RunHeadlessBrowser = true; - static bool PoolForBrowserLogs = false; + + static bool PoolForBrowserLogs = +#if DEBUG + true; +#else + false; +#endif private static async ValueTask WaitForServerAsync(int port, CancellationToken cancellationToken) { diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj index 523c04d26b..66aea279ee 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj @@ -8,6 +8,7 @@ false true + linux-x64 diff --git a/src/Components/benchmarkapps/Wasm.Performance/README.md b/src/Components/benchmarkapps/Wasm.Performance/README.md index 6110956bfc..06fba416c8 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/README.md +++ b/src/Components/benchmarkapps/Wasm.Performance/README.md @@ -9,7 +9,7 @@ The TestApp is a regular BlazorWASM project and can be run using `dotnet run`. T Here are the commands you would need to run it locally inside docker: -1. `dotnet publish -c Release -r linux-x64 Driver/Wasm.Performance.Driver.csproj` +1. `dotnet publish -c Release Driver/Wasm.Performance.Driver.csproj` 2. `docker build -t blazor-local -f ./local.dockerfile . ` 3. `docker run -it blazor-local` diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor new file mode 100644 index 0000000000..4fa366f44c --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor @@ -0,0 +1,122 @@ +@page "/gridrendering" +@inject IJSRuntime JSRuntime +@using Wasm.Performance.TestApp.Shared.FastGrid + +

20 x 200 Grid

+ +
+ + + + + @if (forecasts != null) + { + + } +
+ +@if (forecasts == null) +{ +

(No data assigned)

+} +else if (selectedRenderMode == RenderMode.FastGrid) +{ +

FastGrid represents a minimal, optimized implementation of a grid.

+ + + @context.Date.ToShortDateString() + @context.TemperatureC + @context.TemperatureF + @context.Summary + @context.Date.ToShortDateString() + @context.TemperatureC + @context.TemperatureF + @context.Summary + @context.Date.ToShortDateString() + @context.TemperatureC + @context.TemperatureF + @context.Summary + @context.Date.ToShortDateString() + @context.TemperatureC + @context.TemperatureF + @context.Summary + @context.Date.ToShortDateString() + @context.TemperatureC + @context.TemperatureF + @context.Summary + +} +else if (selectedRenderMode == RenderMode.PlainTable) +{ +

PlainTable represents a minimal but not optimized implementation of a grid.

+ + +} +else if (selectedRenderMode == RenderMode.ComplexTable) +{ +

ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.

+ + +} + +@code { + enum RenderMode { PlainTable, ComplexTable, FastGrid } + + private RenderMode selectedRenderMode = RenderMode.FastGrid; + + private WeatherForecast[] forecasts; + public List Columns { get; set; } = new List +{ + "Date", "TemperatureC", "TemperatureF", "Summary", + "Date", "TemperatureC", "TemperatureF", "Summary", + "Date", "TemperatureC", "TemperatureF", "Summary", + "Date", "TemperatureC", "TemperatureF", "Summary", + "Date", "TemperatureC", "TemperatureF", "Summary", + }; + + private static string[] sampleSummaries = new[] { "Balmy", "Chilly", "Freezing", "Bracing" }; + private static WeatherForecast[] staticSampleDataPage1 = Enumerable.Range(0, 200).Select(CreateSampleDataItem).ToArray(); + private static WeatherForecast[] staticSampleDataPage2 = Enumerable.Range(200, 200).Select(CreateSampleDataItem).ToArray(); + + private static WeatherForecast CreateSampleDataItem(int index) => new WeatherForecast + { + Date = DateTime.Now.Date.AddDays(index), + Summary = sampleSummaries[index % sampleSummaries.Length], + TemperatureC = index, + }; + + void Show() + { + forecasts = staticSampleDataPage1; + } + + void Hide() + { + forecasts = null; + } + + void ChangePage() + { + forecasts = (forecasts == staticSampleDataPage1) ? staticSampleDataPage2 : staticSampleDataPage1; + } + + protected override void OnAfterRender(bool firstRender) + { + BenchmarkEvent.Send(JSRuntime, "Finished rendering table"); + } + + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Cell.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Cell.razor new file mode 100644 index 0000000000..e384a03da3 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Cell.razor @@ -0,0 +1,59 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + @switch (Field) + { + case "Date": + @Item.Date.ToShortDateString() + break; + case "TemperatureC": + @Item.TemperatureC + break; + case "TemperatureF": + @Item.TemperatureF + break; + case "Summary": + @Item.Summary + break; + } + + +@code { + [Parameter] + public WeatherForecast Item { get; set; } + + [CascadingParameter] + public TableComponent ParentTable { get; set; } + + [Parameter] + public string Field { get; set; } + + [Parameter] + public int CellIndex { get; set; } + + [Parameter] + public int RowIndex { get; set; } + + [Parameter] + public bool Selected { get; set; } + + [Parameter] + public string FormatString { get; set; } + + [Parameter] + public Func OnClick { get; set; } + + private protected Dictionary Attributes + { + get + { + var attributes = new Dictionary(); + + attributes["tabindex"] = CellIndex; + + return attributes; + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Row.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Row.razor new file mode 100644 index 0000000000..00716a4c8c --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/Row.razor @@ -0,0 +1,39 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + @foreach (var item in Columns) + { + + + } + + + +@code { + + private bool isSelected = false; + + private string rowStyle => isSelected ? "background-color: lightblue;" : ""; + + [Parameter] + public WeatherForecast Item { get; set; } + + [Parameter] + public List Columns { get; set; } + + [Parameter] + public Func OnClick { get; set; } + + Task OnCellClick(int args) + { + isSelected = !isSelected; + + return OnClick.Invoke(args); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/RowCollection.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/RowCollection.razor new file mode 100644 index 0000000000..180043c5c8 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/RowCollection.razor @@ -0,0 +1,19 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + +@foreach (var item in Data) +{ + +} + + +@code { + [Parameter] + public WeatherForecast[] Data { get; set; } + + [Parameter] + public List Columns { get; set; } + + [Parameter] + public Func OnClick { get; set; } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor new file mode 100644 index 0000000000..70c95ec905 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor @@ -0,0 +1,46 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + + + @foreach (var item in Columns) + { + + } + + + + + + + +
@item
+ + +@code { + [Parameter] + public WeatherForecast[] Data { get; set; } + + [Parameter] + public List Columns { get; set; } + + DateTime t1; + DateTime t2; + Task RefreshComponent(int index) + { + t1 = DateTime.Now; + StateHasChanged(); + return Task.CompletedTask; + } + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + t2 = DateTime.Now; + Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds); + } + return base.OnAfterRenderAsync(firstRender); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/Grid.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/Grid.razor new file mode 100644 index 0000000000..0806655e31 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/Grid.razor @@ -0,0 +1,48 @@ +@typeparam TRowData + +@ChildContent + + + + + @foreach (var col in columns) + { + col.RenderHeader(__builder); + } + + + + @foreach (var item in Data) + { + + @foreach (var col in columns) + { + col.RenderCell(__builder, item); + } + + } + +
+ +@code { + [Parameter(CaptureUnmatchedValues = true)] public Dictionary Attributes { get; set; } + [Parameter] public ICollection Data { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + [Parameter] public Func RowClass { get; set; } + + private List> columns = new List>(); + + internal void AddColumn(GridColumn column) + { + columns.Add(column); + } + + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + // On the first render, we collect the list of columns, then we have to render them. + StateHasChanged(); + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/GridColumn.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/GridColumn.razor new file mode 100644 index 0000000000..aa9c6cfd5b --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/FastGrid/GridColumn.razor @@ -0,0 +1,23 @@ +@typeparam TRowData +@using Microsoft.AspNetCore.Components.Rendering +@code { + [CascadingParameter] public Grid OwnerGrid { get; set; } + [Parameter] public string Title { get; set; } + [Parameter] public TRowData RowData { get; set; } + [Parameter] public RenderFragment ChildContent { get; set; } + + protected override void OnInitialized() + { + OwnerGrid.AddColumn(this); + } + + internal void RenderHeader(RenderTreeBuilder __builder) + { + @Title + } + + internal void RenderCell(RenderTreeBuilder __builder, TRowData rowData) + { + @ChildContent(rowData) + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor index 1beee048bf..722dd4bb22 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor @@ -6,7 +6,8 @@ RenderList | JSON | OrgChart | -Timer +Timer | +Grid
diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Cell.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Cell.razor new file mode 100644 index 0000000000..913436fd99 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Cell.razor @@ -0,0 +1,30 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + @switch (Field) + { + case "Date": + @Item.Date.ToShortDateString() + break; + case "TemperatureC": + @Item.TemperatureC + break; + case "TemperatureF": + @Item.TemperatureF + break; + case "Summary": + @Item.Summary + break; + } + + +@code { + [Parameter] + public WeatherForecast Item { get; set; } + + [Parameter] + public string Field { get; set; } + + [Parameter] + public Func OnClick { get; set; } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Row.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Row.razor new file mode 100644 index 0000000000..fc1b5c2f06 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/Row.razor @@ -0,0 +1,35 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + @foreach (var item in Columns) + { + + + } + + + +@code { + + private bool isSelected = false; + + private string rowStyle => isSelected ? "background-color: lightblue;" : ""; + + [Parameter] + public WeatherForecast Item { get; set; } + + [Parameter] + public List Columns { get; set; } + + [Parameter] + public Func OnClick { get; set; } + + Task OnCellClick(int args) + { + isSelected = !isSelected; + + return OnClick.Invoke(args); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/RowCollection.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/RowCollection.razor new file mode 100644 index 0000000000..180043c5c8 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/RowCollection.razor @@ -0,0 +1,19 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + +@foreach (var item in Data) +{ + +} + + +@code { + [Parameter] + public WeatherForecast[] Data { get; set; } + + [Parameter] + public List Columns { get; set; } + + [Parameter] + public Func OnClick { get; set; } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor new file mode 100644 index 0000000000..ea75beebc7 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor @@ -0,0 +1,44 @@ +@using WeatherForecast = Pages.GridRendering.WeatherForecast + + + + + @foreach (var item in Columns) + { + + } + + + + + +
@item
+ + +@code { + [Parameter] + public WeatherForecast[] Data { get; set; } + + [Parameter] + public List Columns { get; set; } + + DateTime t1; + DateTime t2; + Task RefreshComponent(int index) + { + t1 = DateTime.Now; + StateHasChanged(); + return Task.CompletedTask; + } + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + t2 = DateTime.Now; + Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds); + } + return base.OnAfterRenderAsync(firstRender); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/grid.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/grid.js new file mode 100644 index 0000000000..3acd452ae0 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/grid.js @@ -0,0 +1,105 @@ +import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; +import { receiveEvent } from './util/BenchmarkEvents.js'; +import { BlazorApp } from './util/BlazorApp.js'; +import { setInputValue } from './util/DOM.js'; + +group('Grid', () => { + let app; + + setup(async () => { + app = new BlazorApp(); + await app.start(); + app.navigateTo('gridrendering'); + }); + + teardown(() => { + app.dispose(); + }); + + benchmark('PlainTable: From blank', () => measureRenderGridFromBlank(app), { + setup: () => prepare(app, 'PlainTable', false), + descriptor: { + name: 'blazorwasm/render-plaintable-from-blank', + description: 'Time to render plain table from blank (ms)' + } + }); + + benchmark('PlainTable: Switch pages', () => measureRenderGridSwitchPages(app), { + setup: () => prepare(app, 'PlainTable', true), + descriptor: { + name: 'blazorwasm/render-plaintable-switch-pages', + description: 'Time to render plain table change of page (ms)' + } + }); + + benchmark('ComplexTable: From blank', () => measureRenderGridFromBlank(app), { + setup: () => prepare(app, 'ComplexTable', false), + descriptor: { + name: 'blazorwasm/render-complextable-from-blank', + description: 'Time to render complex table from blank (ms)' + } + }); + + benchmark('ComplexTable: Switch pages', () => measureRenderGridSwitchPages(app), { + setup: () => prepare(app, 'ComplexTable', true), + descriptor: { + name: 'blazorwasm/render-complextable-switch-pages', + description: 'Time to render complex table change of page (ms)' + } + }); + + benchmark('FastGrid: From blank', () => measureRenderGridFromBlank(app), { + setup: () => prepare(app, 'FastGrid', false), + descriptor: { + name: 'blazorwasm/render-fastgrid-from-blank', + description: 'Time to render fast grid from blank (ms)' + } + }); + + benchmark('FastGrid: Switch pages', () => measureRenderGridSwitchPages(app), { + setup: () => prepare(app, 'FastGrid', true), + descriptor: { + name: 'blazorwasm/render-fastgrid-switch-pages', + description: 'Time to render fast grid change of page (ms)' + } + }); +}); + +async function prepare(app, renderMode, populateTable) { + const renderModeSelect = app.window.document.querySelector('#render-mode'); + setInputValue(renderModeSelect, renderMode); + + if (populateTable) { + let nextRenderCompletion = receiveEvent('Finished rendering table'); + app.window.document.querySelector(populateTable ? '#show' : '#hide').click(); + await nextRenderCompletion; + } +} + +async function measureRenderGridFromBlank(app) { + const appDocument = app.window.document; + + let nextRenderCompletion = receiveEvent('Finished rendering table'); + appDocument.querySelector('#hide').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('tbody tr').length !== 0) { + throw new Error('Wrong number of rows rendered'); + } + + nextRenderCompletion = receiveEvent('Finished rendering table'); + appDocument.querySelector('#show').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('tbody tr').length !== 200) { + throw new Error('Wrong number of rows rendered'); + } +} + +async function measureRenderGridSwitchPages(app) { + const appDocument = app.window.document; + + let nextRenderCompletion = receiveEvent('Finished rendering table'); + appDocument.querySelector('#change-page').click(); + await nextRenderCompletion; +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js index c69e1df192..cf923ae082 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js @@ -4,6 +4,7 @@ import './appStartup.js'; import './renderList.js'; import './jsonHandling.js'; import './orgChart.js'; +import './grid.js'; import { getBlazorDownloadSize } from './blazorDownloadSize.js'; new HtmlUI('E2E Performance', '#display'); diff --git a/src/Components/benchmarkapps/Wasm.Performance/dockerfile b/src/Components/benchmarkapps/Wasm.Performance/dockerfile index 8237cb9e59..baf4d03861 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/dockerfile +++ b/src/Components/benchmarkapps/Wasm.Performance/dockerfile @@ -20,10 +20,11 @@ ADD https://api.github.com/repos/dotnet/aspnetcore/git/ref/heads/${gitBranch} /a RUN git init \ && git fetch https://github.com/aspnet/aspnetcore ${gitBranch} \ && git reset --hard FETCH_HEAD \ - && git submodule update --init + && git submodule update --init \ + && git remote add origin https://github.com/aspnet/aspnetcore RUN ./restore.sh -RUN .dotnet/dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj +RUN .dotnet/dotnet publish -c Release --no-restore -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj RUN chmod +x /app/Wasm.Performance.Driver WORKDIR /app diff --git a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile index bccdc856f8..5971ed7ee0 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile +++ b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile @@ -3,7 +3,7 @@ FROM selenium/standalone-chrome:3.141.59-mercury as final ENV StressRunDuration=0 WORKDIR /app -COPY ./Driver/bin/Release/netcoreapp3.1/linux-x64/publish ./ +COPY ./Driver/bin/Release/net5.0/linux-x64/publish ./ COPY ./exec.sh ./ ENTRYPOINT [ "bash", "./exec.sh" ] diff --git a/src/Components/test/E2ETest/Tests/BinaryHttpClientTest.cs b/src/Components/test/E2ETest/Tests/BinaryHttpClientTest.cs index 291b5824ca..0a316bbbca 100644 --- a/src/Components/test/E2ETest/Tests/BinaryHttpClientTest.cs +++ b/src/Components/test/E2ETest/Tests/BinaryHttpClientTest.cs @@ -6,6 +6,7 @@ using BasicTestApp; using BasicTestApp.HttpClientTest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; using TestServer; @@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23366")] public void CanSendAndReceiveBytes() { IssueRequest("/subdir/api/data"); diff --git a/src/Components/test/E2ETest/Tests/BindTest.cs b/src/Components/test/E2ETest/Tests/BindTest.cs index 47f24f7c4b..d281f0c223 100644 --- a/src/Components/test/E2ETest/Tests/BindTest.cs +++ b/src/Components/test/E2ETest/Tests/BindTest.cs @@ -7,6 +7,7 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using Moq; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; @@ -506,6 +507,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23596")] public void CanBindTextboxNullableDouble() { var target = Browser.FindElement(By.Id("textbox-nullable-double")); diff --git a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs index f85aed4bb5..7f62e86441 100644 --- a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] - [QuarantinedTest] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23366")] public void CanRenderTextOnlyComponent() { var appElement = Browser.MountTestComponent(); @@ -400,6 +400,26 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.Equal("Clicks: 2", () => inputElement.GetAttribute("value")); } + [Fact] + public void CanUseFocusExtensionToFocusElement() + { + var appElement = Browser.MountTestComponent(); + var buttonElement = appElement.FindElement(By.Id("focus-button")); + + // Make sure the input element isn't focused when the test begins; we don't want + // the test to pass just because the input started as the focused element + Browser.NotEqual("focus-input", getFocusedElementId); + + // Click the button whose callback focuses the input element + buttonElement.Click(); + + // Verify that the input element is focused + Browser.Equal("focus-input", getFocusedElementId); + + // A local helper that gets the ID of the focused element. + string getFocusedElementId() => Browser.SwitchTo().ActiveElement().GetAttribute("id"); + } + [Fact] public void CanCaptureReferencesToDynamicallyAddedElements() { @@ -464,6 +484,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest] public void CanRenderMarkupBlocks() { var appElement = Browser.MountTestComponent(); diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs index 1076e18832..bdac8d5ba4 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs +++ b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs @@ -6,6 +6,7 @@ using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; @@ -50,6 +51,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23596")] public void ShowsErrorNotification_OnError_Reload() { var causeErrorButton = Browser.Exists(By.Id("throw-simple-exception")); diff --git a/src/Components/test/E2ETest/Tests/EventBubblingTest.cs b/src/Components/test/E2ETest/Tests/EventBubblingTest.cs index 1b63f5ab4d..5fc5c037d4 100644 --- a/src/Components/test/E2ETest/Tests/EventBubblingTest.cs +++ b/src/Components/test/E2ETest/Tests/EventBubblingTest.cs @@ -48,6 +48,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23366")] public void BubblingStandardEvent_FiredOnElementWithoutHandler() { Browser.FindElement(By.Id("button-without-onclick")).Click(); diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index 90d298502d..15495dc7da 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -301,6 +301,68 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.Equal(new[] { "Must accept terms", "Must not be evil" }, messagesAccessor); } + [Fact] + public void InputRadioGroupWithoutNameInteractsWithEditContext() + { + var appElement = MountTypicalValidationComponent(); + var airlineInputs = appElement.FindElement(By.ClassName("airline")).FindElements(By.TagName("input")); + var unknownAirlineInput = airlineInputs.First(i => i.GetAttribute("value").Equals("Unknown")); + var bestAirlineInput = airlineInputs.First(i => i.GetAttribute("value").Equals("BestAirline")); + var messagesAccessor = CreateValidationMessagesAccessor(appElement); + + // Validate unselected inputs + Assert.All(airlineInputs.Where(i => i != unknownAirlineInput), i => Browser.False(() => i.Selected)); + + // Validate selected inputs + Browser.True(() => unknownAirlineInput.Selected); + + // InputRadio emits additional attributes + Browser.True(() => unknownAirlineInput.GetAttribute("extra").Equals("additional")); + + // Validates on edit + Assert.All(airlineInputs, i => Browser.Equal("valid", () => i.GetAttribute("class"))); + bestAirlineInput.Click(); + Assert.All(airlineInputs, i => Browser.Equal("modified valid", () => i.GetAttribute("class"))); + + // Can become invalid + unknownAirlineInput.Click(); + Assert.All(airlineInputs, i => Browser.Equal("modified invalid", () => i.GetAttribute("class"))); + Browser.Equal(new[] { "Pick a valid airline." }, messagesAccessor); + } + + [Fact] + public void InputRadioGroupsWithNamesNestedInteractWithEditContext() + { + var appElement = MountTypicalValidationComponent(); + var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]")); + var group = appElement.FindElement(By.ClassName("nested-radio-group")); + var countryInputs = group.FindElements(By.Name("country")); + var colorInputs = group.FindElements(By.Name("color")); + + // Validate group counts + Assert.Equal(3, countryInputs.Count); + Assert.Equal(4, colorInputs.Count); + + // Validate unselected inputs + Assert.All(countryInputs, i => Browser.False(() => i.Selected)); + Assert.All(colorInputs, i => Browser.False(() => i.Selected)); + + // Invalidates on submit + Assert.All(countryInputs, i => Browser.Equal("valid", () => i.GetAttribute("class"))); + Assert.All(colorInputs, i => Browser.Equal("valid", () => i.GetAttribute("class"))); + submitButton.Click(); + Assert.All(countryInputs, i => Browser.Equal("invalid", () => i.GetAttribute("class"))); + Assert.All(colorInputs, i => Browser.Equal("invalid", () => i.GetAttribute("class"))); + + // Validates on edit + countryInputs.First().Click(); + Assert.All(countryInputs, i => Browser.Equal("modified valid", () => i.GetAttribute("class"))); + Assert.All(colorInputs, i => Browser.Equal("invalid", () => i.GetAttribute("class"))); + + colorInputs.First().Click(); + Assert.All(colorInputs, i => Browser.Equal("modified valid", () => i.GetAttribute("class"))); + } + [Fact] public void CanWireUpINotifyPropertyChangedToEditContext() { diff --git a/src/Components/test/E2ETest/Tests/PerformanceTest.cs b/src/Components/test/E2ETest/Tests/PerformanceTest.cs index f7187a4557..546a2214d7 100644 --- a/src/Components/test/E2ETest/Tests/PerformanceTest.cs +++ b/src/Components/test/E2ETest/Tests/PerformanceTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; @@ -35,6 +36,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23366")] public void BenchmarksRunWithoutError() { // In CI, we only verify that the benchmarks run without throwing any diff --git a/src/Components/test/E2ETest/Tests/RoutingTest.cs b/src/Components/test/E2ETest/Tests/RoutingTest.cs index 8d9c4b45ea..29fec5e599 100644 --- a/src/Components/test/E2ETest/Tests/RoutingTest.cs +++ b/src/Components/test/E2ETest/Tests/RoutingTest.cs @@ -9,6 +9,7 @@ using BasicTestApp.RouterTest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using Xunit; @@ -479,14 +480,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Browser.Equal(0, () => BrowserScrollY); } + [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23596")] + public void PreventDefault_CanBlockNavigation_ForInternalNavigation_PreventDefaultTarget() + => PreventDefault_CanBlockNavigation("internal", "target"); + [Theory] [InlineData("external", "ancestor")] [InlineData("external", "target")] [InlineData("external", "descendant")] [InlineData("internal", "ancestor")] - [InlineData("internal", "target")] [InlineData("internal", "descendant")] - public void PreventDefault_CanBlockNavigation(string navigationType, string whereToPreventDefault) + public virtual void PreventDefault_CanBlockNavigation(string navigationType, string whereToPreventDefault) { SetUrlViaPushState("/PreventDefaultCases"); var app = Browser.MountTestComponent(); diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index 3923c0d5c0..f47ac9084a 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -10,8 +10,6 @@ false - - true diff --git a/src/Components/test/testassets/BasicTestApp/ElementFocusComponent.razor b/src/Components/test/testassets/BasicTestApp/ElementFocusComponent.razor new file mode 100644 index 0000000000..0e98e8c67c --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/ElementFocusComponent.razor @@ -0,0 +1,13 @@ +@using Microsoft.JSInterop + + + + +@code { + private ElementReference inputReference; + + private async Task FocusInput() + { + await inputReference.FocusAsync(); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor index 7a306ef999..89a53802ee 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor @@ -40,6 +40,32 @@ @person.TicketClass

+

+ + Airline: +
+ @foreach (var airline in (Airline[])Enum.GetValues(typeof(Airline))) + { + + @airline.ToString(); +
+ } +
+

+

+ Pick one color and one country: + + + red
+ japan
+ green
+ yemen
+ blue
+ latvia
+ orange
+
+
+

Accepts terms:

@@ -109,11 +135,27 @@ [Required, EnumDataType(typeof(TicketClass))] public TicketClass TicketClass { get; set; } + [Required] + [Range(typeof(Airline), nameof(Airline.BestAirline), nameof(Airline.NoNameAirline), ErrorMessage = "Pick a valid airline.")] + public Airline Airline { get; set; } = Airline.Unknown; + + [Required, EnumDataType(typeof(Color))] + public Color? FavoriteColor { get; set; } = null; + + [Required, EnumDataType(typeof(Country))] + public Country? Country { get; set; } = null; + public string Username { get; set; } } enum TicketClass { Economy, Premium, First } + enum Airline { BestAirline, CoolAirline, NoNameAirline, Unknown } + + enum Color { Red, Green, Blue, Orange } + + enum Country { Japan, Yemen, Latvia } + List submissionLog = new List(); // So we can assert about the callbacks void HandleValidSubmit() diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 8b2738e72b..0a8ce0a5cc 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -20,6 +20,7 @@ + diff --git a/src/Hosting/Hosting/ref/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/ref/Microsoft.AspNetCore.Hosting.csproj index 72e71d1c31..11eb4ad78e 100644 --- a/src/Hosting/Hosting/ref/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/ref/Microsoft.AspNetCore.Hosting.csproj @@ -16,7 +16,7 @@ - +
diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs index ef7688e153..e7b4ef62e9 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Hosting { serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey); - foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var value in urls.Split(';', StringSplitOptions.RemoveEmptyEntries)) { addresses.Add(value); } @@ -180,7 +180,7 @@ namespace Microsoft.AspNetCore.Hosting } else { - model.ErrorDetails = new ExceptionDetails[0]; + model.ErrorDetails = Array.Empty(); } var errorPage = new ErrorPage(model); diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index 060b3fd9a7..ae935412b4 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -306,7 +306,7 @@ namespace Microsoft.AspNetCore.Hosting { serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey); - foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var value in urls.Split(';', StringSplitOptions.RemoveEmptyEntries)) { addresses.Add(value); } diff --git a/src/Hosting/Hosting/src/Internal/WebHostOptions.cs b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs index 88ceac1dac..1f78e3e255 100644 --- a/src/Hosting/Hosting/src/Internal/WebHostOptions.cs +++ b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs @@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Hosting } var list = new List(); - foreach (var part in value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var part in value.Split(';', StringSplitOptions.RemoveEmptyEntries)) { var trimmedPart = part; if (!string.IsNullOrEmpty(trimmedPart)) diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index c78cef809d..c0e3e77284 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core hosting infrastructure and startup logic for web applications. @@ -26,7 +26,7 @@ - + diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs index 8d0c8da6b2..ca689f8092 100644 --- a/src/Hosting/test/FunctionalTests/ShutdownTests.cs +++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests public ShutdownTests(ITestOutputHelper output) : base(output) { } [ConditionalFact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23610")] [OSSkipCondition(OperatingSystems.Windows)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task ShutdownTestRun() diff --git a/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs b/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs index b83bf4eeea..e06ae7b012 100644 --- a/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs +++ b/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs @@ -254,6 +254,11 @@ namespace Microsoft.AspNetCore.Http switch (attr) { case Attr.Delimiter: + if (ch == (char)0) + { + _valueEnd = _offset; + _trailingStart = _offset; + } _mode = Mode.Produce; break; case Attr.Quote: diff --git a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs index 0591abdce6..ed0c103006 100644 --- a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs +++ b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs @@ -80,7 +80,9 @@ namespace Microsoft.AspNetCore.Http { Encoding.BigEndianUnicode }, { Encoding.Unicode }, { Encoding.UTF32 }, +#pragma warning disable CS0618, MSLIB0001 // Type or member is obsolete { Encoding.UTF7 }, +#pragma warning restore CS0618, MSLIB0001 // Type or member is obsolete { Encoding.UTF8 } }; diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs index a3bfbb6958..ae8e1ed6ca 100644 --- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs +++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs @@ -77,7 +77,6 @@ namespace Microsoft.AspNetCore.Http.Json options ??= ResolveSerializerOptions(response.HttpContext); response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; - response.StatusCode = StatusCodes.Status200OK; return JsonSerializer.SerializeAsync(response.Body, value!, options, cancellationToken); } @@ -150,7 +149,6 @@ namespace Microsoft.AspNetCore.Http.Json options ??= ResolveSerializerOptions(response.HttpContext); response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; - response.StatusCode = StatusCodes.Status200OK; return JsonSerializer.SerializeAsync(response.Body, value, type, options, cancellationToken); } diff --git a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs index 74add9b9e1..be01516515 100644 --- a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs +++ b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs @@ -88,6 +88,23 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests } } + [Fact] + public async Task WriteAsJsonAsyncGeneric_CustomStatusCode_StatusCodeUnchanged() + { + // Arrange + var body = new MemoryStream(); + var context = new DefaultHttpContext(); + context.Response.Body = body; + + // Act + context.Response.StatusCode = StatusCodes.Status418ImATeapot; + await context.Response.WriteAsJsonAsync(1); + + // Assert + Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType); + Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode); + } + [Fact] public async Task WriteAsJsonAsyncGeneric_WithContentType_JsonResponseWithCustomContentType() { @@ -223,6 +240,23 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data); } + [Fact] + public async Task WriteAsJsonAsync_CustomStatusCode_StatusCodeUnchanged() + { + // Arrange + var body = new MemoryStream(); + var context = new DefaultHttpContext(); + context.Response.Body = body; + + // Act + context.Response.StatusCode = StatusCodes.Status418ImATeapot; + await context.Response.WriteAsJsonAsync(1, typeof(int)); + + // Assert + Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType); + Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode); + } + public class TestObject { public string? StringProperty { get; set; } diff --git a/src/Http/Http/src/Features/FormFeature.cs b/src/Http/Http/src/Features/FormFeature.cs index c5a288e0bb..d7f1888139 100644 --- a/src/Http/Http/src/Features/FormFeature.cs +++ b/src/Http/Http/src/Features/FormFeature.cs @@ -276,7 +276,9 @@ namespace Microsoft.AspNetCore.Http.Features private static Encoding FilterEncoding(Encoding? encoding) { // UTF-7 is insecure and should not be honored. UTF-8 will succeed for most cases. +#pragma warning disable CS0618, MSLIB0001 // Type or member is obsolete if (encoding == null || Encoding.UTF7.Equals(encoding)) +#pragma warning restore CS0618, MSLIB0001 // Type or member is obsolete { return Encoding.UTF8; } diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs index 6c74a6769b..d6542a23d7 100644 --- a/src/Http/Http/src/Internal/RequestCookieCollection.cs +++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs @@ -57,6 +57,9 @@ namespace Microsoft.AspNetCore.Http } public static RequestCookieCollection Parse(IList values) + => ParseInternal(values, AppContext.TryGetSwitch(ResponseCookies.EnableCookieNameEncoding, out var enabled) && enabled); + + internal static RequestCookieCollection ParseInternal(IList values, bool enableCookieNameEncoding) { if (values.Count == 0) { @@ -75,7 +78,7 @@ namespace Microsoft.AspNetCore.Http for (var i = 0; i < cookies.Count; i++) { var cookie = cookies[i]; - var name = Uri.UnescapeDataString(cookie.Name.Value); + var name = enableCookieNameEncoding ? Uri.UnescapeDataString(cookie.Name.Value) : cookie.Name.Value; var value = Uri.UnescapeDataString(cookie.Value.Value); store[name] = value; } diff --git a/src/Http/Http/src/Internal/ResponseCookies.cs b/src/Http/Http/src/Internal/ResponseCookies.cs index 1de354e342..d9adfb69f1 100644 --- a/src/Http/Http/src/Internal/ResponseCookies.cs +++ b/src/Http/Http/src/Internal/ResponseCookies.cs @@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Http ///
internal class ResponseCookies : IResponseCookies { + internal const string EnableCookieNameEncoding = "Microsoft.AspNetCore.Http.EnableCookieNameEncoding"; + internal bool _enableCookieNameEncoding = AppContext.TryGetSwitch(EnableCookieNameEncoding, out var enabled) && enabled; + /// /// Create a new wrapper. /// @@ -33,7 +36,7 @@ namespace Microsoft.AspNetCore.Http public void Append(string key, string value) { var setCookieHeaderValue = new SetCookieHeaderValue( - Uri.EscapeDataString(key), + _enableCookieNameEncoding ? Uri.EscapeDataString(key) : key, Uri.EscapeDataString(value)) { Path = "/" @@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Http } var setCookieHeaderValue = new SetCookieHeaderValue( - Uri.EscapeDataString(key), + _enableCookieNameEncoding ? Uri.EscapeDataString(key) : key, Uri.EscapeDataString(value)) { Domain = options.Domain, @@ -83,7 +86,7 @@ namespace Microsoft.AspNetCore.Http throw new ArgumentNullException(nameof(options)); } - var encodedKeyPlusEquals = Uri.EscapeDataString(key) + "="; + var encodedKeyPlusEquals = (_enableCookieNameEncoding ? Uri.EscapeDataString(key) : key) + "="; bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); diff --git a/src/Http/Http/test/HeaderDictionaryTests.cs b/src/Http/Http/test/HeaderDictionaryTests.cs index 26113f53c0..e9cf32ebe3 100644 --- a/src/Http/Http/test/HeaderDictionaryTests.cs +++ b/src/Http/Http/test/HeaderDictionaryTests.cs @@ -103,5 +103,18 @@ namespace Microsoft.AspNetCore.Http Assert.Throws(() => headers.Remove(new KeyValuePair("header1", "value1"))); Assert.Throws(() => headers.Remove("header1")); } + + [Fact] + public void GetCommaSeparatedValues_WorksForUnquotedHeaderValuesEndingWithSpace() + { + var headers = new HeaderDictionary + { + { "Via", "value " }, + }; + + var result = headers.GetCommaSeparatedValues("Via"); + + Assert.Equal(new[]{"value "}, result); + } } -} \ No newline at end of file +} diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs index da9a846277..ddd17f143d 100644 --- a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs +++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs @@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Http Assert.Null(cookies0["key0"]); Assert.False(cookies0.ContainsKey("key0")); - var newCookies = new[] { "name0=value0%2C", "%5Ename1=value1" }; + var newCookies = new[] { "name0=value0%2C", "name1=value1" }; request.Headers["Cookie"] = newCookies; cookies0 = RequestCookieCollection.Parse(newCookies); @@ -183,7 +183,7 @@ namespace Microsoft.AspNetCore.Http Assert.Equal(cookies0, cookies1); Assert.Equal(2, cookies1.Count); Assert.Equal("value0,", cookies1["name0"]); - Assert.Equal("value1", cookies1["^name1"]); + Assert.Equal("value1", cookies1["name1"]); Assert.Equal(newCookies, request.Headers["Cookie"]); var cookies2 = new RequestCookieCollection(new Dictionary() diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs index 0d3cf069ad..8d3dad77f6 100644 --- a/src/Http/Http/test/RequestCookiesCollectionTests.cs +++ b/src/Http/Http/test/RequestCookiesCollectionTests.cs @@ -9,28 +9,13 @@ namespace Microsoft.AspNetCore.Http.Tests { public class RequestCookiesCollectionTests { - public static TheoryData UnEscapesKeyValues_Data - { - get - { - // key, value, expected - return new TheoryData - { - { "key=value", "key", "value" }, - { "key%2C=%21value", "key,", "!value" }, - { "ke%23y%2C=val%5Eue", "ke#y,", "val^ue" }, - { "base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==" }, - { "base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==" }, - }; - } - } - [Theory] - [MemberData(nameof(UnEscapesKeyValues_Data))] - public void UnEscapesKeyValues( - string input, - string expectedKey, - string expectedValue) + [InlineData("key=value", "key", "value")] + [InlineData("key%2C=%21value", "key%2C", "!value")] + [InlineData("ke%23y%2C=val%5Eue", "ke%23y%2C", "val^ue")] + [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")] + [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")] + public void UnEscapesValues(string input, string expectedKey, string expectedValue) { var cookies = RequestCookieCollection.Parse(new StringValues(input)); @@ -38,5 +23,20 @@ namespace Microsoft.AspNetCore.Http.Tests Assert.Equal(expectedKey, cookies.Keys.Single()); Assert.Equal(expectedValue, cookies[expectedKey]); } + + [Theory] + [InlineData("key=value", "key", "value")] + [InlineData("key%2C=%21value", "key,", "!value")] + [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")] + [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")] + [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")] + public void AppContextSwitchUnEscapesKeysAndValues(string input, string expectedKey, string expectedValue) + { + var cookies = RequestCookieCollection.ParseInternal(new StringValues(input), enableCookieNameEncoding: true); + + Assert.Equal(1, cookies.Count); + Assert.Equal(expectedKey, cookies.Keys.Single()); + Assert.Equal(expectedValue, cookies[expectedKey]); + } } } diff --git a/src/Http/Http/test/ResponseCookiesTest.cs b/src/Http/Http/test/ResponseCookiesTest.cs index 23eea058de..8e4e60aaa9 100644 --- a/src/Http/Http/test/ResponseCookiesTest.cs +++ b/src/Http/Http/test/ResponseCookiesTest.cs @@ -88,31 +88,45 @@ namespace Microsoft.AspNetCore.Http.Tests Assert.Contains($"max-age={maxAgeTime.TotalSeconds.ToString()}", cookieHeaderValues[0]); } - public static TheoryData EscapesKeyValuesBeforeSettingCookieData - { - get - { - // key, value, object pool, expected - return new TheoryData - { - { "key", "value", "key=value" }, - { "key,", "!value", "key%2C=%21value" }, - { "ke#y,", "val^ue", "ke%23y%2C=val%5Eue" }, - { "base64", "QUI+REU/Rw==", "base64=QUI%2BREU%2FRw%3D%3D" }, - }; - } - } - [Theory] - [MemberData(nameof(EscapesKeyValuesBeforeSettingCookieData))] - public void EscapesKeyValuesBeforeSettingCookie( - string key, - string value, - string expected) + [InlineData("value", "key=value")] + [InlineData("!value", "key=%21value")] + [InlineData("val^ue", "key=val%5Eue")] + [InlineData("QUI+REU/Rw==", "key=QUI%2BREU%2FRw%3D%3D")] + public void EscapesValuesBeforeSettingCookie(string value, string expected) { var headers = new HeaderDictionary(); var cookies = new ResponseCookies(headers); + cookies.Append("key", value); + + var cookieHeaderValues = headers[HeaderNames.SetCookie]; + Assert.Single(cookieHeaderValues); + Assert.StartsWith(expected, cookieHeaderValues[0]); + } + + [Theory] + [InlineData("key,")] + [InlineData("ke@y")] + public void InvalidKeysThrow(string key) + { + var headers = new HeaderDictionary(); + var cookies = new ResponseCookies(headers); + + Assert.Throws(() => cookies.Append(key, "1")); + } + + [Theory] + [InlineData("key", "value", "key=value")] + [InlineData("key,", "!value", "key%2C=%21value")] + [InlineData("ke#y,", "val^ue", "ke%23y%2C=val%5Eue")] + [InlineData("base64", "QUI+REU/Rw==", "base64=QUI%2BREU%2FRw%3D%3D")] + public void AppContextSwitchEscapesKeysAndValuesBeforeSettingCookie(string key, string value, string expected) + { + var headers = new HeaderDictionary(); + var cookies = new ResponseCookies(headers); + cookies._enableCookieNameEncoding = true; + cookies.Append(key, value); var cookieHeaderValues = headers[HeaderNames.SetCookie]; diff --git a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs index 85a91f7d67..30de18e604 100644 --- a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs +++ b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp.cs @@ -77,6 +77,8 @@ namespace Microsoft.AspNetCore.WebUtilities [System.Diagnostics.DebuggerStepThroughAttribute] public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.Task DrainBufferAsync(System.IO.Pipelines.PipeWriter destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task DrainBufferAsync(System.IO.Stream destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs index bea934bb43..2c96ed29ae 100644 --- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Internal; @@ -185,8 +186,25 @@ namespace Microsoft.AspNetCore.WebUtilities // unspooled content. Copy the FileStream content first when available. if (FileStream != null) { - await FileStream.FlushAsync(cancellationToken); + // We make a new stream for async reads from disk and async writes to the destination + await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true); + await readStream.CopyToAsync(destination, cancellationToken); + + // This is created with delete on close + await FileStream.DisposeAsync(); + FileStream = null; + } + + await PagedByteBuffer.MoveToAsync(destination, cancellationToken); + } + + public async Task DrainBufferAsync(PipeWriter destination, CancellationToken cancellationToken = default) + { + // When not null, FileStream always has "older" spooled content. The PagedByteBuffer always has "newer" + // unspooled content. Copy the FileStream content first when available. + if (FileStream != null) + { // We make a new stream for async reads from disk and async writes to the destination await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true); diff --git a/src/Http/WebUtilities/src/FormPipeReader.cs b/src/Http/WebUtilities/src/FormPipeReader.cs index 3ce6e03b29..bcb553458d 100644 --- a/src/Http/WebUtilities/src/FormPipeReader.cs +++ b/src/Http/WebUtilities/src/FormPipeReader.cs @@ -47,8 +47,10 @@ namespace Microsoft.AspNetCore.WebUtilities public FormPipeReader(PipeReader pipeReader, Encoding encoding) { +#pragma warning disable CS0618, MSLIB0001 // Type or member is obsolete if (encoding == Encoding.UTF7) { +#pragma warning restore CS0618, MSLIB0001 // Type or member is obsolete throw new ArgumentException("UTF7 is unsupported and insecure. Please select a different encoding."); } diff --git a/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs index f0a51656ad..58c16bd605 100644 --- a/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs +++ b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs @@ -34,20 +34,22 @@ namespace Microsoft.AspNetCore.WebUtilities MediaTypeHeaderValue.TryParse(section.ContentType, out var sectionMediaType); var streamEncoding = sectionMediaType?.Encoding; +#pragma warning disable CS0618, MSLIB0001 // Type or member is obsolete if (streamEncoding == null || streamEncoding == Encoding.UTF7) +#pragma warning restore CS0618, MSLIB0001 // Type or member is obsolete { streamEncoding = Encoding.UTF8; } using (var reader = new StreamReader( - section.Body, + section.Body, streamEncoding, detectEncodingFromByteOrderMarks: true, - bufferSize: 1024, + bufferSize: 1024, leaveOpen: true)) { return await reader.ReadToEndAsync(); } - } + } } } diff --git a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs index 3d39adde9a..3e19a8c31b 100644 --- a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs +++ b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs @@ -371,24 +371,6 @@ namespace Microsoft.AspNetCore.WebUtilities Assert.Equal(0, bufferingStream.Length); } - [Fact] - public async Task DrainBufferAsync_IncludesContentPossiblyBufferedByFileStream() - { - // We want to ensure that the FileStream (which has a 1-byte buffer) flushes prior to the other read stream reading input. - // Arrange - var input = new byte[] { 3, }; - using var bufferingStream = new FileBufferingWriteStream(0, tempFileDirectoryAccessor: () => TempDirectory); - bufferingStream.Write(input, 0, input.Length); - var memoryStream = new MemoryStream(); - - // Act - await bufferingStream.DrainBufferAsync(memoryStream, default); - - // Assert - Assert.Equal(input, memoryStream.ToArray()); - Assert.Equal(0, bufferingStream.Length); - } - public void Dispose() { try diff --git a/src/Identity/ApiAuthorization.IdentityServer/src/Options/ClientCollection.cs b/src/Identity/ApiAuthorization.IdentityServer/src/Options/ClientCollection.cs index b34e809eb3..9f3dd10e1d 100644 --- a/src/Identity/ApiAuthorization.IdentityServer/src/Options/ClientCollection.cs +++ b/src/Identity/ApiAuthorization.IdentityServer/src/Options/ClientCollection.cs @@ -68,11 +68,13 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer ///
/// The client id for the single page application. /// The to configure the default single page application. - public void AddIdentityServerSPA(string clientId, Action configure) + public Client AddIdentityServerSPA(string clientId, Action configure) { var app = ClientBuilder.IdentityServerSPA(clientId); configure(app); - Add(app.Build()); + var client = app.Build(); + Add(client); + return client; } /// @@ -80,11 +82,13 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer /// /// The client id for the single page application. /// The to configure the default single page application. - public void AddSPA(string clientId, Action configure) + public Client AddSPA(string clientId, Action configure) { var app = ClientBuilder.SPA(clientId); configure(app); - Add(app.Build()); + var client = app.Build(); + Add(client); + return client; } /// @@ -92,11 +96,13 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer /// /// The client id for the single page application. /// The to configure the native application. - public void AddNativeApp(string clientId, Action configure) + public Client AddNativeApp(string clientId, Action configure) { var app = ClientBuilder.NativeApp(clientId); configure(app); - Add(app.Build()); + var client = app.Build(); + Add(client); + return client; } } } diff --git a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs index 61b1891d26..1b1c625055 100644 --- a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs +++ b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.netcoreapp.cs @@ -137,7 +137,6 @@ namespace Microsoft.AspNetCore.Identity public virtual bool IsSignedIn(System.Security.Claims.ClaimsPrincipal principal) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public virtual System.Threading.Tasks.Task IsTwoFactorClientRememberedAsync(TUser user) { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] protected virtual System.Threading.Tasks.Task LockedOut(TUser user) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public virtual System.Threading.Tasks.Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure) { throw null; } diff --git a/src/Identity/Core/src/SignInManager.cs b/src/Identity/Core/src/SignInManager.cs index 5aa7aad531..0717a8af4b 100644 --- a/src/Identity/Core/src/SignInManager.cs +++ b/src/Identity/Core/src/SignInManager.cs @@ -144,17 +144,17 @@ namespace Microsoft.AspNetCore.Identity { if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user))) { - Logger.LogWarning(0, "User {userId} cannot sign in without a confirmed email.", await UserManager.GetUserIdAsync(user)); + Logger.LogWarning(0, "User cannot sign in without a confirmed email."); return false; } if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user))) { - Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user)); + Logger.LogWarning(1, "User cannot sign in without a confirmed phone number."); return false; } if (Options.SignIn.RequireConfirmedAccount && !(await _confirmation.IsConfirmedAsync(UserManager, user))) { - Logger.LogWarning(4, "User {userId} cannot sign in without a confirmed account.", await UserManager.GetUserIdAsync(user)); + Logger.LogWarning(4, "User cannot sign in without a confirmed account."); return false; } return true; @@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Identity return SignInResult.Success; } - Logger.LogWarning(2, "User {userId} failed to provide the correct password.", await UserManager.GetUserIdAsync(user)); + Logger.LogWarning(2, "User failed to provide the correct password."); if (UserManager.SupportsUserLockout && lockoutOnFailure) { @@ -840,10 +840,10 @@ namespace Microsoft.AspNetCore.Identity ///
/// The user. /// A locked out SignInResult - protected virtual async Task LockedOut(TUser user) + protected virtual Task LockedOut(TUser user) { - Logger.LogWarning(3, "User {userId} is currently locked out.", await UserManager.GetUserIdAsync(user)); - return SignInResult.LockedOut; + Logger.LogWarning(3, "User is currently locked out."); + return Task.FromResult(SignInResult.LockedOut); } /// diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index fab5fd0d88..afecd4d235 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -610,7 +610,7 @@ namespace Microsoft.AspNetCore.Identity /// The name to normalize. /// A normalized value representing the specified . public virtual string NormalizeName(string name) - => (KeyNormalizer == null) ? name : KeyNormalizer.NormalizeName(name); + => (KeyNormalizer == null) ? name : KeyNormalizer.NormalizeName(name); /// /// Normalize email for consistent comparisons. @@ -618,7 +618,7 @@ namespace Microsoft.AspNetCore.Identity /// The email to normalize. /// A normalized value representing the specified . public virtual string NormalizeEmail(string email) - => (KeyNormalizer == null) ? email : KeyNormalizer.NormalizeEmail(email); + => (KeyNormalizer == null) ? email : KeyNormalizer.NormalizeEmail(email); private string ProtectPersonalData(string data) { @@ -716,7 +716,7 @@ namespace Microsoft.AspNetCore.Identity var success = result != PasswordVerificationResult.Failed; if (!success) { - Logger.LogWarning(0, "Invalid password for user {userId}.", await GetUserIdAsync(user)); + Logger.LogWarning(0, "Invalid password for user."); } return success; } @@ -763,7 +763,7 @@ namespace Microsoft.AspNetCore.Identity var hash = await passwordStore.GetPasswordHashAsync(user, CancellationToken); if (hash != null) { - Logger.LogWarning(1, "User {userId} already has a password.", await GetUserIdAsync(user)); + Logger.LogWarning(1, "User already has a password."); return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); } var result = await UpdatePasswordHash(passwordStore, user, password); @@ -804,7 +804,7 @@ namespace Microsoft.AspNetCore.Identity } return await UpdateUserAsync(user); } - Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user)); + Logger.LogWarning(2, "Change password failed for user."); return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); } @@ -865,7 +865,7 @@ namespace Microsoft.AspNetCore.Identity var stamp = await securityStore.GetSecurityStampAsync(user, CancellationToken); if (stamp == null) { - Logger.LogWarning(15, "GetSecurityStampAsync for user {userId} failed because stamp was null.", await GetUserIdAsync(user)); + Logger.LogWarning(15, "GetSecurityStampAsync for user failed because stamp was null."); throw new InvalidOperationException(Resources.NullSecurityStamp); } return stamp; @@ -1021,7 +1021,7 @@ namespace Microsoft.AspNetCore.Identity var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey); if (existingUser != null) { - Logger.LogWarning(4, "AddLogin for user {userId} failed because it was already associated with another user.", await GetUserIdAsync(user)); + Logger.LogWarning(4, "AddLogin for user failed because it was already associated with another user."); return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()); } await loginStore.AddLoginAsync(user, login, CancellationToken); @@ -1216,7 +1216,7 @@ namespace Microsoft.AspNetCore.Identity var normalizedRole = NormalizeName(role); if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken)) { - return await UserAlreadyInRoleError(user, role); + return UserAlreadyInRoleError(role); } await userRoleStore.AddToRoleAsync(user, normalizedRole, CancellationToken); return await UpdateUserAsync(user); @@ -1249,7 +1249,7 @@ namespace Microsoft.AspNetCore.Identity var normalizedRole = NormalizeName(role); if (await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken)) { - return await UserAlreadyInRoleError(user, role); + return UserAlreadyInRoleError(role); } await userRoleStore.AddToRoleAsync(user, normalizedRole, CancellationToken); } @@ -1277,21 +1277,21 @@ namespace Microsoft.AspNetCore.Identity var normalizedRole = NormalizeName(role); if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken)) { - return await UserNotInRoleError(user, role); + return UserNotInRoleError(role); } await userRoleStore.RemoveFromRoleAsync(user, normalizedRole, CancellationToken); return await UpdateUserAsync(user); } - private async Task UserAlreadyInRoleError(TUser user, string role) + private IdentityResult UserAlreadyInRoleError(string role) { - Logger.LogWarning(5, "User {userId} is already in role {role}.", await GetUserIdAsync(user), role); + Logger.LogWarning(5, "User is already in role {role}.", role); return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); } - private async Task UserNotInRoleError(TUser user, string role) + private IdentityResult UserNotInRoleError(string role) { - Logger.LogWarning(6, "User {userId} is not in role {role}.", await GetUserIdAsync(user), role); + Logger.LogWarning(6, "User is not in role {role}.", role); return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); } @@ -1322,7 +1322,7 @@ namespace Microsoft.AspNetCore.Identity var normalizedRole = NormalizeName(role); if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken)) { - return await UserNotInRoleError(user, role); + return UserNotInRoleError(role); } await userRoleStore.RemoveFromRoleAsync(user, normalizedRole, CancellationToken); } @@ -1627,7 +1627,7 @@ namespace Microsoft.AspNetCore.Identity if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber)) { - Logger.LogWarning(7, "Change phone number for user {userId} failed with invalid token.", await GetUserIdAsync(user)); + Logger.LogWarning(7, "Change phone number for user failed with invalid token."); return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } await store.SetPhoneNumberAsync(user, phoneNumber, CancellationToken); @@ -1689,7 +1689,7 @@ namespace Microsoft.AspNetCore.Identity } // Make sure the token is valid and the stamp matches - return VerifyUserTokenAsync(user, Options.Tokens.ChangePhoneNumberTokenProvider, ChangePhoneNumberTokenPurpose+":"+ phoneNumber, token); + return VerifyUserTokenAsync(user, Options.Tokens.ChangePhoneNumberTokenProvider, ChangePhoneNumberTokenPurpose + ":" + phoneNumber, token); } /// @@ -1725,7 +1725,7 @@ namespace Microsoft.AspNetCore.Identity if (!result) { - Logger.LogWarning(9, "VerifyUserTokenAsync() failed with purpose: {purpose} for user {userId}.", purpose, await GetUserIdAsync(user)); + Logger.LogWarning(9, "VerifyUserTokenAsync() failed with purpose: {purpose} for user.", purpose); } return result; } @@ -1827,7 +1827,7 @@ namespace Microsoft.AspNetCore.Identity var result = await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user); if (!result) { - Logger.LogWarning(10, $"{nameof(VerifyTwoFactorTokenAsync)}() failed for user {await GetUserIdAsync(user)}."); + Logger.LogWarning(10, $"{nameof(VerifyTwoFactorTokenAsync)}() failed for user."); } return result; } @@ -2000,7 +2000,7 @@ namespace Microsoft.AspNetCore.Identity if (!await store.GetLockoutEnabledAsync(user, CancellationToken)) { - Logger.LogWarning(11, "Lockout for user {userId} failed because lockout is not enabled for this user.", await GetUserIdAsync(user)); + Logger.LogWarning(11, "Lockout for user failed because lockout is not enabled for this user."); return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()); } await store.SetLockoutEndDateAsync(user, lockoutEnd, CancellationToken); @@ -2029,7 +2029,7 @@ namespace Microsoft.AspNetCore.Identity { return await UpdateUserAsync(user); } - Logger.LogWarning(12, "User {userId} is locked out.", await GetUserIdAsync(user)); + Logger.LogWarning(12, "User is locked out."); await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken); await store.ResetAccessFailedCountAsync(user, CancellationToken); @@ -2503,7 +2503,7 @@ namespace Microsoft.AspNetCore.Identity } if (errors.Count > 0) { - Logger.LogWarning(13, "User {userId} validation failed: {errors}.", await GetUserIdAsync(user), string.Join(";", errors.Select(e => e.Code))); + Logger.LogWarning(13, "User validation failed: {errors}.", string.Join(";", errors.Select(e => e.Code))); return IdentityResult.Failed(errors.ToArray()); } return IdentityResult.Success; @@ -2535,7 +2535,7 @@ namespace Microsoft.AspNetCore.Identity } if (!isValid) { - Logger.LogWarning(14, "User {userId} password validation failed: {errors}.", await GetUserIdAsync(user), string.Join(";", errors.Select(e => e.Code))); + Logger.LogWarning(14, "User password validation failed: {errors}.", string.Join(";", errors.Select(e => e.Code))); return IdentityResult.Failed(errors.ToArray()); } return IdentityResult.Success; diff --git a/src/Identity/Specification.Tests/src/IdentitySpecificationTestBase.cs b/src/Identity/Specification.Tests/src/IdentitySpecificationTestBase.cs index 4e67e26253..a9988a47e3 100644 --- a/src/Identity/Specification.Tests/src/IdentitySpecificationTestBase.cs +++ b/src/Identity/Specification.Tests/src/IdentitySpecificationTestBase.cs @@ -486,7 +486,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var result = await userMgr.RemoveFromRoleAsync(user, roleName); IdentityResultAssert.IsFailure(result, _errorDescriber.UserNotInRole(roleName)); - IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User {await userMgr.GetUserIdAsync(user)} is not in role {roleName}."); + IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User is not in role {roleName}."); } /// @@ -507,7 +507,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName)); Assert.True(await userMgr.IsInRoleAsync(user, roleName)); IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, roleName), _errorDescriber.UserAlreadyInRole(roleName)); - IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User {await userMgr.GetUserIdAsync(user)} is already in role {roleName}."); + IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User is already in role {roleName}."); } /// diff --git a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs index 4bc1a37559..73d48be2f8 100644 --- a/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs +++ b/src/Identity/Specification.Tests/src/UserManagerSpecificationTests.cs @@ -252,11 +252,11 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(newUser)); var error = _errorDescriber.InvalidUserName(""); IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, ""), error); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User validation failed: {error.Code}."); error = _errorDescriber.DuplicateUserName(newUsername); IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, newUsername), error); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User validation failed: {error.Code}."); } /// @@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Identity.Test SetUserPasswordHash(user, manager.PasswordHasher.HashPassword(user, "New")); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); Assert.False(await manager.CheckPasswordAsync(user, "password")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Invalid password for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Invalid password for user."); Assert.True(await manager.CheckPasswordAsync(user, "New")); } @@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.UserValidators.Clear(); manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); } /// @@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.UserValidators.Clear(); manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); } /// @@ -394,7 +394,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.UserValidators.Add(new AlwaysBadValidator()); var result = await manager.CreateAsync(user); IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code};{AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User validation failed: {AlwaysBadValidator.ErrorMessage.Code};{AlwaysBadValidator.ErrorMessage.Code}."); Assert.Equal(2, result.Errors.Count()); } @@ -442,7 +442,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); } /// @@ -530,7 +530,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); } /// @@ -545,7 +545,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.PasswordValidators.Clear(); manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.CreateAsync(user, "password"), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); } /// @@ -625,7 +625,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.True(await manager.HasPasswordAsync(user)); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), "User already has a password set."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} already has a password."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User already has a password."); } /// @@ -829,7 +829,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); IdentityResultAssert.IsFailure(result, "Incorrect password."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Change password failed for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Change password failed for user."); } /// @@ -906,7 +906,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); var result = await manager.AddLoginAsync(user, login); IdentityResultAssert.IsFailure(result, _errorDescriber.LoginAlreadyAssociated()); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"AddLogin for user {await manager.GetUserIdAsync(user)} failed because it was already associated with another user."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"AddLogin for user failed because it was already associated with another user."); } // Email tests @@ -1028,7 +1028,7 @@ namespace Microsoft.AspNetCore.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); } @@ -1050,7 +1050,7 @@ namespace Microsoft.AspNetCore.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); Assert.NotNull(stamp); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ResetPassword for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ResetPassword for user."); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); } @@ -1074,13 +1074,13 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.True(await manager.VerifyUserTokenAsync(user, "Static", "test", token)); Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test2", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test2 for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test2 for user."); Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test", token + "a")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user."); Assert.False(await manager.VerifyUserTokenAsync(user2, "Static", "test", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user2)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user."); } /// @@ -1120,7 +1120,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); Assert.False(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user."); } /// @@ -1138,7 +1138,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.NotNull(token); IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, "password", "newpassword")); IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, token), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user."); Assert.False(await manager.IsEmailConfirmedAsync(user)); } @@ -1161,7 +1161,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); Assert.True(await mgr.IsLockedOutAsync(user)); Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User is locked out."); Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); } @@ -1187,7 +1187,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); Assert.True(await mgr.IsLockedOutAsync(user)); Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User is locked out."); Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); } @@ -1243,7 +1243,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); Assert.True(await mgr.IsLockedOutAsync(user)); Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User is locked out."); Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); } @@ -1277,7 +1277,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.False(await mgr.GetLockoutEnabledAsync(user)); IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset()), "Lockout is not enabled for this user."); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"Lockout for user {await mgr.GetUserIdAsync(user)} failed because lockout is not enabled for this user."); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"Lockout for user failed because lockout is not enabled for this user."); Assert.False(await mgr.IsLockedOutAsync(user)); } @@ -1407,7 +1407,7 @@ namespace Microsoft.AspNetCore.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-111-1111 for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-111-1111 for user."); Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); @@ -1450,7 +1450,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// /// Task [Fact] - [QuarantinedTest] public async Task ChangePhoneNumberFailsWithWrongPhoneNumber() { var manager = CreateManager(); @@ -1471,7 +1470,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// /// Task [Fact] - [QuarantinedTest] public async Task CanVerifyPhoneNumber() { var manager = CreateManager(); @@ -1488,8 +1486,8 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, "bogus", num1)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, "bogus", num2)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:{num1} for user {await manager.GetUserIdAsync(user)}."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:{num2} for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:{num1} for user."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:{num2} for user."); } /// @@ -1519,7 +1517,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// /// Task [Fact] - [QuarantinedTest] public async Task CanChangeEmailOnlyIfEmailSame() { var manager = CreateManager(); @@ -1604,7 +1601,7 @@ namespace Microsoft.AspNetCore.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.boop", "bogus"), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:whatevah@foo.boop for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:whatevah@foo.boop for user."); Assert.False(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), oldEmail); Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); @@ -1628,7 +1625,7 @@ namespace Microsoft.AspNetCore.Identity.Test var token1 = await manager.GenerateChangeEmailTokenAsync(user, "forgot@alrea.dy"); IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "oops@foo.boop", token1), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:oops@foo.boop for user { await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:oops@foo.boop for user."); Assert.False(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), oldEmail); Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); @@ -1661,7 +1658,7 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); } Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user."); } /// @@ -1858,7 +1855,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.NotNull(token); IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user."); } /// @@ -1874,7 +1871,7 @@ namespace Microsoft.AspNetCore.Identity.Test var token = await manager.GenerateTwoFactorTokenAsync(user, "Phone"); Assert.NotNull(token); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Email", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user."); } /// @@ -1888,7 +1885,7 @@ namespace Microsoft.AspNetCore.Identity.Test var user = CreateTestUser(phoneNumber: "4251234567"); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Phone", "bogus")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user."); } /// 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 a79002149b..67a31b63e9 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 @@ -20,7 +20,7 @@
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml.cs index 896a7656a5..7a65c2edb2 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml.cs @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { - _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); + _logger.LogInformation("User logged in with {LoginProvider} provider.", info.LoginProvider); return LocalRedirect(returnUrl); } if (result.IsLockedOut) 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 491619e107..2ced1baa31 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 @@ -13,7 +13,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 e83237fd5a..a1d9d27d77 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 @@ -15,12 +15,12 @@
- +
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml.cs index 4b64207a87..428557335f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml.cs @@ -131,17 +131,17 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal if (result.Succeeded) { - _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); + _logger.LogInformation("User logged in with 2fa."); return LocalRedirect(returnUrl); } else if (result.IsLockedOut) { - _logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + _logger.LogWarning("User account locked out."); return RedirectToPage("./Lockout"); } else { - _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); + _logger.LogWarning("Invalid authenticator code entered."); ModelState.AddModelError(string.Empty, "Invalid authenticator code."); return Page(); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml.cs index db2b4ffacb..26ab6a4eae 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml.cs @@ -114,17 +114,17 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Internal if (result.Succeeded) { - _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); + _logger.LogInformation("User logged in with a recovery code."); return LocalRedirect(returnUrl ?? Url.Content("~/")); } if (result.IsLockedOut) { - _logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + _logger.LogWarning("User account locked out."); return RedirectToPage("./Lockout"); } else { - _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); + _logger.LogWarning("Invalid recovery code entered."); ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); return Page(); } 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 8710f25598..4fb53617e5 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 @@ -13,17 +13,17 @@
- +
- +
- +
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 4939c7fd5a..89f9318864 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 @@ -1,4 +1,4 @@ -@page +@page @model DeletePersonalDataModel @{ ViewData["Title"] = "Delete Personal Data"; @@ -21,7 +21,7 @@ {
- +
} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml.cs index b393082805..498bd9feba 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml.cs @@ -105,15 +105,14 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal } var result = await _userManager.DeleteAsync(user); - var userId = await _userManager.GetUserIdAsync(user); if (!result.Succeeded) { - throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'."); + throw new InvalidOperationException($"Unexpected error occurred deleting user."); } await _signInManager.SignOutAsync(); - _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + _logger.LogInformation("User deleted themselves."); return Redirect("~/"); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Disable2fa.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Disable2fa.cshtml.cs index f12c23038f..08604a1f70 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Disable2fa.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Disable2fa.cshtml.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal if (!await _userManager.GetTwoFactorEnabledAsync(user)) { - throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled."); + throw new InvalidOperationException($"Cannot disable 2FA as it's not currently enabled."); } return Page(); @@ -76,12 +76,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); if (!disable2faResult.Succeeded) { - throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'."); + throw new InvalidOperationException($"Unexpected error occurred disabling 2FA."); } - _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); + _logger.LogInformation("User has disabled 2fa."); StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"; return RedirectToPage("./TwoFactorAuthentication"); } } -} \ No newline at end of file +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DownloadPersonalData.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DownloadPersonalData.cshtml.cs index d0fdadaf93..3d10008438 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DownloadPersonalData.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DownloadPersonalData.cshtml.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User)); + _logger.LogInformation("User asked for their personal data."); // Only include personal data for download var personalData = new Dictionary(); diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml index 37e649546a..829e2a8075 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Email.cshtml @@ -20,7 +20,7 @@
} - else + else { @@ -28,7 +28,7 @@
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/EnableAuthenticator.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/EnableAuthenticator.cshtml.cs index 75e5efdf26..5702207f1b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/EnableAuthenticator.cshtml.cs @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal await _userManager.SetTwoFactorEnabledAsync(user, true); var userId = await _userManager.GetUserIdAsync(user); - _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); + _logger.LogInformation("User has enabled 2FA with an authenticator app."); StatusMessage = "Your authenticator app has been verified."; diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ExternalLogins.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ExternalLogins.cshtml.cs index 1360a5431b..c4676a9fbc 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ExternalLogins.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ExternalLogins.cshtml.cs @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal var info = await _signInManager.GetExternalLoginInfoAsync(userId); if (info == null) { - throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{userId}'."); + throw new InvalidOperationException($"Unexpected error occurred loading external login info."); } var result = await _userManager.AddLoginAsync(user, info); diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/GenerateRecoveryCodes.cshtml.cs index d33ff2e3a3..347acec21b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/GenerateRecoveryCodes.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -68,8 +68,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); if (!isTwoFactorEnabled) { - var userId = await _userManager.GetUserIdAsync(user); - throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled."); + throw new InvalidOperationException($"Cannot generate recovery codes because they do not have 2FA enabled."); } return Page(); @@ -84,16 +83,15 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal } var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); - var userId = await _userManager.GetUserIdAsync(user); if (!isTwoFactorEnabled) { - throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled."); + throw new InvalidOperationException($"Cannot generate recovery codes as they do not have 2FA enabled."); } var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); RecoveryCodes = recoveryCodes.ToArray(); - _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + _logger.LogInformation("User has generated new 2FA recovery codes."); StatusMessage = "You have generated new recovery codes."; return RedirectToPage("./ShowRecoveryCodes"); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ResetAuthenticator.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ResetAuthenticator.cshtml.cs index 213a016a02..f83fb9c77c 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ResetAuthenticator.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ResetAuthenticator.cshtml.cs @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal await _userManager.SetTwoFactorEnabledAsync(user, false); await _userManager.ResetAuthenticatorKeyAsync(user); var userId = await _userManager.GetUserIdAsync(user); - _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); + _logger.LogInformation("User has reset their authentication app key."); await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; @@ -83,4 +83,4 @@ namespace Microsoft.AspNetCore.Identity.UI.V3.Pages.Account.Manage.Internal return RedirectToPage("./EnableAuthenticator"); } } -} \ No newline at end of file +} 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 ab43dcc3f6..eb72f8eb1a 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 @@ -17,12 +17,12 @@
- +
- +
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 ad901d7d5b..5a1c5961ac 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 @@ -14,17 +14,17 @@
- +
- +
- +
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 5a28bd1b9e..6892e4e82a 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 @@ -14,17 +14,17 @@
- +
- +
- +
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 f7dc967d63..2d7dd0ed2c 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 @@ -20,7 +20,7 @@
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml.cs index 5b580b865c..18b9752170 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml.cs @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { - _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); + _logger.LogInformation("User logged in with {LoginProvider} provider.", info.LoginProvider); return LocalRedirect(returnUrl); } if (result.IsLockedOut) 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 bd7460e007..d55361f492 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 @@ -13,7 +13,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 704f48aa34..f5157d41fc 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 @@ -15,12 +15,12 @@
- +
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml.cs index ae71b4a0a8..c02fd82b5d 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml.cs @@ -130,17 +130,17 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal if (result.Succeeded) { - _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); + _logger.LogInformation("User logged in with 2fa."); return LocalRedirect(returnUrl); } else if (result.IsLockedOut) { - _logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + _logger.LogWarning("User account locked out."); return RedirectToPage("./Lockout"); } else { - _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); + _logger.LogWarning("Invalid authenticator code entered."); ModelState.AddModelError(string.Empty, "Invalid authenticator code."); return Page(); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml.cs index b3b4af95c3..629c516df7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml.cs @@ -113,17 +113,17 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal if (result.Succeeded) { - _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); + _logger.LogInformation("User logged in with a recovery code."); return LocalRedirect(returnUrl ?? Url.Content("~/")); } if (result.IsLockedOut) { - _logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + _logger.LogWarning("User account locked out."); return RedirectToPage("./Lockout"); } else { - _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); + _logger.LogWarning("Invalid recovery code entered."); ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); return Page(); } 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 98a07a3c08..e20e095e39 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 @@ -13,17 +13,17 @@
- +
- +
- +
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 fc9280ffea..9f6f65b448 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 @@ -20,7 +20,7 @@ {
- +
} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml.cs index 3a918fccb4..34372d197e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml.cs @@ -108,12 +108,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal var userId = await _userManager.GetUserIdAsync(user); if (!result.Succeeded) { - throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'."); + throw new InvalidOperationException($"Unexpected error occurred deleting user."); } await _signInManager.SignOutAsync(); - _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + _logger.LogInformation("User deleted themselves."); return Redirect("~/"); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Disable2fa.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Disable2fa.cshtml.cs index 1cb5c66b5f..1a844ca1d6 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Disable2fa.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Disable2fa.cshtml.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal if (!await _userManager.GetTwoFactorEnabledAsync(user)) { - throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled."); + throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled."); } return Page(); @@ -76,12 +76,12 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); if (!disable2faResult.Succeeded) { - throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'."); + throw new InvalidOperationException($"Unexpected error occurred disabling 2FA."); } - _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); + _logger.LogInformation("User has disabled 2fa."); StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"; return RedirectToPage("./TwoFactorAuthentication"); } } -} \ No newline at end of file +} diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DownloadPersonalData.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DownloadPersonalData.cshtml.cs index 38d077f014..378556879e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DownloadPersonalData.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DownloadPersonalData.cshtml.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User)); + _logger.LogInformation("User asked for their personal data."); // Only include personal data for download var personalData = new Dictionary(); diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml index c4943a78b8..b7f4c9e7d7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Email.cshtml @@ -22,7 +22,7 @@
} - else + else { @@ -30,7 +30,7 @@
- +
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/EnableAuthenticator.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/EnableAuthenticator.cshtml.cs index 069f6c3d7b..66cab7ef9b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/EnableAuthenticator.cshtml.cs @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal await _userManager.SetTwoFactorEnabledAsync(user, true); var userId = await _userManager.GetUserIdAsync(user); - _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); + _logger.LogInformation("User has enabled 2FA with an authenticator app."); StatusMessage = "Your authenticator app has been verified."; diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ExternalLogins.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ExternalLogins.cshtml.cs index 0a4754bf3a..2d00de4082 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ExternalLogins.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ExternalLogins.cshtml.cs @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal var info = await _signInManager.GetExternalLoginInfoAsync(userId); if (info == null) { - throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{userId}'."); + throw new InvalidOperationException($"Unexpected error occurred loading external login info."); } var result = await _userManager.AddLoginAsync(user, info); diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/GenerateRecoveryCodes.cshtml.cs index afe71d3ed1..d8f54f7732 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/GenerateRecoveryCodes.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -68,8 +68,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); if (!isTwoFactorEnabled) { - var userId = await _userManager.GetUserIdAsync(user); - throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled."); + throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled."); } return Page(); @@ -84,16 +83,15 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal } var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); - var userId = await _userManager.GetUserIdAsync(user); if (!isTwoFactorEnabled) { - throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled."); + throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled."); } var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); RecoveryCodes = recoveryCodes.ToArray(); - _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + _logger.LogInformation("User has generated new 2FA recovery codes."); StatusMessage = "You have generated new recovery codes."; return RedirectToPage("./ShowRecoveryCodes"); } diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ResetAuthenticator.cshtml.cs b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ResetAuthenticator.cshtml.cs index b8463b6976..c87b96e770 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ResetAuthenticator.cshtml.cs +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ResetAuthenticator.cshtml.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal await _userManager.SetTwoFactorEnabledAsync(user, false); await _userManager.ResetAuthenticatorKeyAsync(user); var userId = await _userManager.GetUserIdAsync(user); - _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); + _logger.LogInformation("User has reset their authentication app key."); await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; @@ -82,4 +82,4 @@ namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal return RedirectToPage("./EnableAuthenticator"); } } -} \ No newline at end of file +} 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 3154d95ff4..c324c12125 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 @@ -17,12 +17,12 @@
- +
- +
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 2fcb00e2d9..faa4a90888 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 @@ -14,17 +14,17 @@
- +
- +
- +
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 27bc951b03..147db271e7 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 @@ -14,17 +14,17 @@
- +
- +
- +
diff --git a/src/Identity/test/Identity.Test/SignInManagerTest.cs b/src/Identity/test/Identity.Test/SignInManagerTest.cs index 2135608e81..05839a3580 100644 --- a/src/Identity/test/Identity.Test/SignInManagerTest.cs +++ b/src/Identity/test/Identity.Test/SignInManagerTest.cs @@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.IsLockedOut); - Assert.Contains($"User {user.Id} is currently locked out.", logger.LogMessages); + Assert.Contains($"User is currently locked out.", logger.LogMessages); manager.Verify(); } @@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.IsLockedOut); - Assert.Contains($"User {user.Id} is currently locked out.", logger.LogMessages); + Assert.Contains($"User is currently locked out.", logger.LogMessages); manager.Verify(); } @@ -771,7 +771,7 @@ namespace Microsoft.AspNetCore.Identity.Test // Assert Assert.False(result.Succeeded); Assert.False(checkResult.Succeeded); - Assert.Contains($"User {user.Id} failed to provide the correct password.", logger.LogMessages); + Assert.Contains($"User failed to provide the correct password.", logger.LogMessages); manager.Verify(); context.Verify(); } @@ -881,7 +881,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); - var message = $"User {user.Id} cannot sign in without a confirmed email."; + var message = $"User cannot sign in without a confirmed email."; if (!confirmed) { Assert.Contains(message, logger.LogMessages); @@ -935,7 +935,7 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); - var message = $"User {user.Id} cannot sign in without a confirmed phone number."; + var message = $"User cannot sign in without a confirmed phone number."; if (!confirmed) { Assert.Contains(message, logger.LogMessages); diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json index b73521f14f..40db20ee63 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json @@ -18,9 +18,9 @@ "author": "Microsoft", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/dotnet/extensions/issues" + "url": "https://github.com/dotnet/aspnetcore/issues" }, - "homepage": "https://github.com/dotnet/extensions/tree/master/src/JSInterop#readme", + "homepage": "https://github.com/dotnet/aspnetcore/tree/master/src/JSInterop", "files": [ "dist/**" ], diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index a6e9730b45..298472bd82 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -234,7 +234,7 @@ export module DotNet { } function findJSFunction(identifier: string): Function { - if (cachedJSFunctions.hasOwnProperty(identifier)) { + if (Object.prototype.hasOwnProperty.call(cachedJSFunctions, identifier)) { return cachedJSFunctions[identifier]; } diff --git a/src/Middleware/CORS/src/Infrastructure/CorsPolicy.cs b/src/Middleware/CORS/src/Infrastructure/CorsPolicy.cs index 8c7529d30e..54f48fe16a 100644 --- a/src/Middleware/CORS/src/Infrastructure/CorsPolicy.cs +++ b/src/Middleware/CORS/src/Infrastructure/CorsPolicy.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure { get { - if (Headers == null || Headers.Count != 1 || Headers.Count == 1 && Headers[0] != "*") + if (Headers == null || Headers.Count != 1 || Headers[0] != "*") { return false; } @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure { get { - if (Methods == null || Methods.Count != 1 || Methods.Count == 1 && Methods[0] != "*") + if (Methods == null || Methods.Count != 1 || Methods[0] != "*") { return false; } @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure { get { - if (Origins == null || Origins.Count != 1 || Origins.Count == 1 && Origins[0] != "*") + if (Origins == null || Origins.Count != 1 || Origins[0] != "*") { return false; } diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs index 16c612cae9..394be846dc 100644 --- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs +++ b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs @@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter LoggerMessage.Define(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Below MaxConcurrentRequests limit, running request immediately. Current active requests: {ActiveRequests}"); private static readonly Action _requestRejectedQueueFull = - LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not availible' error"); + LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not available' error"); internal static void RequestEnqueued(ILogger logger, int activeRequests) { diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs index 08d6338df2..09d378e4e1 100644 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs +++ b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs @@ -13,9 +13,9 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter private readonly int _maxTotalRequest; private readonly SemaphoreSlim _serverSemaphore; - private object _totalRequestsLock = new object(); + private int _totalRequests; - public int TotalRequests { get; private set; } + public int TotalRequests => _totalRequests; public QueuePolicy(IOptions options) { @@ -44,14 +44,12 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter // a return value of 'true' indicates that the request may proceed // _serverSemaphore.Release is *not* called in this method, it is called externally when requests leave the server - lock (_totalRequestsLock) - { - if (TotalRequests >= _maxTotalRequest) - { - return new ValueTask(false); - } + int totalRequests = Interlocked.Increment(ref _totalRequests); - TotalRequests++; + if (totalRequests > _maxTotalRequest) + { + Interlocked.Decrement(ref _totalRequests); + return new ValueTask(false); } Task task = _serverSemaphore.WaitAsync(); @@ -67,10 +65,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter { _serverSemaphore.Release(); - lock (_totalRequestsLock) - { - TotalRequests--; - } + Interlocked.Decrement(ref _totalRequests); } public void Dispose() diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs index 2315a6bdeb..5e5eaec3b7 100644 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs +++ b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter public int MaxConcurrentRequests { get; set; } /// - /// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'. + /// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailable'. /// This option is highly application dependant, and must be configured by the application. /// public int RequestQueueLimit { get; set; } diff --git a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs b/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs index cc8c40457c..8d569322b7 100644 --- a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs +++ b/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests public class QueuePolicyTests { [Fact] - public void DoesNotWaitIfSpaceAvailible() + public void DoesNotWaitIfSpaceAvailable() { using var s = TestUtils.CreateQueuePolicy(2); @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests } [Fact] - public async Task WaitsIfNoSpaceAvailible() + public async Task WaitsIfNoSpaceAvailable() { using var s = TestUtils.CreateQueuePolicy(1); Assert.True(await s.TryEnterAsync().OrTimeout()); @@ -36,6 +36,27 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests Assert.True(await waitingTask.OrTimeout()); } + [Fact] + public void DoesNotWaitIfQueueFull() + { + using var s = TestUtils.CreateQueuePolicy(2, 1); + + var t1 = s.TryEnterAsync(); + Assert.True(t1.IsCompleted); + Assert.True(t1.Result); + + var t2 = s.TryEnterAsync(); + Assert.True(t2.IsCompleted); + Assert.True(t2.Result); + + var t3 = s.TryEnterAsync(); + Assert.False(t3.IsCompleted); + + var t4 = s.TryEnterAsync(); + Assert.True(t4.IsCompleted); + Assert.False(t4.Result); + } + [Fact] public async Task IsEncapsulated() { diff --git a/src/Middleware/Localization/src/CookieRequestCultureProvider.cs b/src/Middleware/Localization/src/CookieRequestCultureProvider.cs index 63b89fb611..5a17457848 100644 --- a/src/Middleware/Localization/src/CookieRequestCultureProvider.cs +++ b/src/Middleware/Localization/src/CookieRequestCultureProvider.cs @@ -4,7 +4,6 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Localization { @@ -13,7 +12,7 @@ namespace Microsoft.AspNetCore.Localization ///
public class CookieRequestCultureProvider : RequestCultureProvider { - private static readonly char[] _cookieSeparator = new[] { '|' }; + private static readonly char _cookieSeparator = '|'; private static readonly string _culturePrefix = "c="; private static readonly string _uiCulturePrefix = "uic="; @@ -60,9 +59,7 @@ namespace Microsoft.AspNetCore.Localization throw new ArgumentNullException(nameof(requestCulture)); } - var seperator = _cookieSeparator[0].ToString(); - - return string.Join(seperator, + return string.Join(_cookieSeparator, $"{_culturePrefix}{requestCulture.Culture.Name}", $"{_uiCulturePrefix}{requestCulture.UICulture.Name}"); } diff --git a/src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs b/src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs index 3cb94ab005..491d803d94 100644 --- a/src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs +++ b/src/Middleware/ResponseCaching/src/ResponseCachingPolicyProvider.cs @@ -34,11 +34,12 @@ namespace Microsoft.AspNetCore.ResponseCaching public virtual bool AllowCacheLookup(ResponseCachingContext context) { var requestHeaders = context.HttpContext.Request.Headers; + var cacheControl = requestHeaders[HeaderNames.CacheControl]; // Verify request cache-control parameters - if (!StringValues.IsNullOrEmpty(requestHeaders[HeaderNames.CacheControl])) + if (!StringValues.IsNullOrEmpty(cacheControl)) { - if (HeaderUtilities.ContainsCacheDirective(requestHeaders[HeaderNames.CacheControl], CacheControlHeaderValue.NoCacheString)) + if (HeaderUtilities.ContainsCacheDirective(cacheControl, CacheControlHeaderValue.NoCacheString)) { context.Logger.RequestWithNoCacheNotCacheable(); return false; diff --git a/src/Middleware/SpaServices.Extensions/src/Proxying/SpaProxy.cs b/src/Middleware/SpaServices.Extensions/src/Proxying/SpaProxy.cs index 04d7e697a1..20f516540d 100644 --- a/src/Middleware/SpaServices.Extensions/src/Proxying/SpaProxy.cs +++ b/src/Middleware/SpaServices.Extensions/src/Proxying/SpaProxy.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.SpaServices.Extensions.Proxy // Don't forward User-Agent/Accept because of https://github.com/aspnet/JavaScriptServices/issues/1469 // Others just aren't applicable in proxy scenarios - private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Accept", "Connection", "Host", "User-Agent", "Upgrade", "Sec-WebSocket-Key", "Sec-WebSocket-Version" }; + private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Accept", "Connection", "Host", "User-Agent", "Upgrade", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol", "Sec-WebSocket-Version" }; public static HttpClient CreateHttpClientForProxy(TimeSpan requestTimeout) { @@ -209,6 +209,10 @@ namespace Microsoft.AspNetCore.SpaServices.Extensions.Proxy using (var client = new ClientWebSocket()) { + foreach (var protocol in context.WebSockets.WebSocketRequestedProtocols) + { + client.Options.AddSubProtocol(protocol); + } foreach (var headerEntry in context.Request.Headers) { if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase)) diff --git a/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.csproj index ce0671621e..2ae7b2695e 100644 --- a/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -2,6 +2,7 @@ $(DefaultNetCoreTargetFramework) + annotations diff --git a/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp.cs b/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp.cs index 84ca15ad39..267c99ef8a 100644 --- a/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp.cs +++ b/src/Mvc/Mvc.Abstractions/ref/Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp.cs @@ -33,10 +33,10 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions public partial class ActionDescriptor { public ActionDescriptor() { } - public System.Collections.Generic.IList ActionConstraints { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo AttributeRouteInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IList? ActionConstraints { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo? AttributeRouteInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Collections.Generic.IList BoundProperties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual string DisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual string? DisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Collections.Generic.IList EndpointMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Collections.Generic.IList FilterDescriptors { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions { public ActionInvokerProviderContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext) { } public Microsoft.AspNetCore.Mvc.ActionContext ActionContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial interface IActionDescriptorProvider { @@ -143,8 +143,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { public ApiDescription() { } public Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor ActionDescriptor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string HttpMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? GroupName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? HttpMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Collections.Generic.IList ParameterDescriptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public System.Collections.Generic.IDictionary Properties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public string RelativePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } @@ -160,21 +160,21 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public partial class ApiParameterDescription { public ApiParameterDescription() { } - public Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo BindingInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public object DefaultValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo? BindingInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object? DefaultValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool IsRequired { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata ModelMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor ParameterDescriptor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo RouteInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo? RouteInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource Source { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Type Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ApiParameterRouteInfo { public ApiParameterRouteInfo() { } - public System.Collections.Generic.IEnumerable Constraints { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public object DefaultValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Collections.Generic.IEnumerable? Constraints { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object? DefaultValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool IsOptional { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ApiRequestFormat @@ -194,9 +194,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public ApiResponseType() { } public System.Collections.Generic.IList ApiResponseFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool IsDefaultResponse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata ModelMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata? ModelMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public int StatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public System.Type Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Type? Type { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial interface IApiDescriptionProvider { @@ -218,8 +218,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters public ActionExecutedContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters, object controller) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } public virtual bool Canceled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual object Controller { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public virtual System.Exception Exception { get { throw null; } set { } } - public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo ExceptionDispatchInfo { get { throw null; } set { } } + public virtual System.Exception? Exception { get { throw null; } set { } } + public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo? ExceptionDispatchInfo { get { throw null; } set { } } public virtual bool ExceptionHandled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } @@ -228,26 +228,27 @@ namespace Microsoft.AspNetCore.Mvc.Filters public ActionExecutingContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters, System.Collections.Generic.IDictionary actionArguments, object controller) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } public virtual System.Collections.Generic.IDictionary ActionArguments { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual object Controller { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Mvc.IActionResult? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public delegate System.Threading.Tasks.Task ActionExecutionDelegate(); public partial class AuthorizationFilterContext : Microsoft.AspNetCore.Mvc.Filters.FilterContext { public AuthorizationFilterContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } - public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Mvc.IActionResult? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ExceptionContext : Microsoft.AspNetCore.Mvc.Filters.FilterContext { public ExceptionContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } public virtual System.Exception Exception { get { throw null; } set { } } - public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo ExceptionDispatchInfo { get { throw null; } set { } } + public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo? ExceptionDispatchInfo { get { throw null; } set { } } public virtual bool ExceptionHandled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Mvc.IActionResult? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public abstract partial class FilterContext : Microsoft.AspNetCore.Mvc.ActionContext { public FilterContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters) { } public virtual System.Collections.Generic.IList Filters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + [return: System.Diagnostics.CodeAnalysis.MaybeNullAttribute] public TMetadata FindEffectivePolicy() where TMetadata : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata { throw null; } public bool IsEffectivePolicy(TMetadata policy) where TMetadata : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata { throw null; } } @@ -349,15 +350,15 @@ namespace Microsoft.AspNetCore.Mvc.Filters { public ResourceExecutedContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } public virtual bool Canceled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual System.Exception Exception { get { throw null; } set { } } - public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo ExceptionDispatchInfo { get { throw null; } set { } } + public virtual System.Exception? Exception { get { throw null; } set { } } + public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo? ExceptionDispatchInfo { get { throw null; } set { } } public virtual bool ExceptionHandled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Mvc.IActionResult? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class ResourceExecutingContext : Microsoft.AspNetCore.Mvc.Filters.FilterContext { public ResourceExecutingContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters, System.Collections.Generic.IList valueProviderFactories) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } - public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public virtual Microsoft.AspNetCore.Mvc.IActionResult? Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Collections.Generic.IList ValueProviderFactories { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public delegate System.Threading.Tasks.Task ResourceExecutionDelegate(); @@ -366,8 +367,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters public ResultExecutedContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, System.Collections.Generic.IList filters, Microsoft.AspNetCore.Mvc.IActionResult result, object controller) : base (default(Microsoft.AspNetCore.Mvc.ActionContext), default(System.Collections.Generic.IList)) { } public virtual bool Canceled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual object Controller { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public virtual System.Exception Exception { get { throw null; } set { } } - public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo ExceptionDispatchInfo { get { throw null; } set { } } + public virtual System.Exception? Exception { get { throw null; } set { } } + public virtual System.Runtime.ExceptionServices.ExceptionDispatchInfo? ExceptionDispatchInfo { get { throw null; } set { } } public virtual bool ExceptionHandled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual Microsoft.AspNetCore.Mvc.IActionResult Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } @@ -382,7 +383,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters } namespace Microsoft.AspNetCore.Mvc.Formatters { - public partial class FormatterCollection : System.Collections.ObjectModel.Collection + public partial class FormatterCollection : System.Collections.ObjectModel.Collection where TFormatter : notnull { public FormatterCollection() { } public FormatterCollection(System.Collections.Generic.IList list) { } @@ -426,7 +427,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters internal InputFormatterResult() { } public bool HasError { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public bool IsModelSet { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object? Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public static Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult Failure() { throw null; } public static System.Threading.Tasks.Task FailureAsync() { throw null; } public static Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult NoValue() { throw null; } @@ -445,8 +446,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters public virtual Microsoft.Extensions.Primitives.StringSegment ContentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual bool ContentTypeIsServerDefined { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public virtual Microsoft.AspNetCore.Http.HttpContext HttpContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } - public virtual object Object { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } - public virtual System.Type ObjectType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } + public virtual object? Object { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } + public virtual System.Type? ObjectType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] protected set { } } } public partial class OutputFormatterWriteContext : Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext { @@ -460,18 +461,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { public BindingInfo() { } public BindingInfo(Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo other) { } - public string BinderModelName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public System.Type BinderType { get { throw null; } set { } } - public Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource BindingSource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? BinderModelName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Type? BinderType { get { throw null; } set { } } + public Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? BindingSource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior EmptyBodyBehavior { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider PropertyFilterProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public System.Func RequestPredicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public static Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo GetBindingInfo(System.Collections.Generic.IEnumerable attributes) { throw null; } - public static Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo GetBindingInfo(System.Collections.Generic.IEnumerable attributes, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata modelMetadata) { throw null; } + public Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider? PropertyFilterProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public System.Func? RequestPredicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public static Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo? GetBindingInfo(System.Collections.Generic.IEnumerable attributes) { throw null; } + public static Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo? GetBindingInfo(System.Collections.Generic.IEnumerable attributes, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata modelMetadata) { throw null; } public bool TryApplyBindingInfo(Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata modelMetadata) { throw null; } } [System.Diagnostics.DebuggerDisplayAttribute("Source: {DisplayName}")] - public partial class BindingSource : System.IEquatable + public partial class BindingSource : System.IEquatable { public static readonly Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource Body; public static readonly Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource Custom; @@ -489,11 +490,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public bool IsFromRequest { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public bool IsGreedy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public virtual bool CanAcceptDataFrom(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource bindingSource) { throw null; } - public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource other) { throw null; } - public override bool Equals(object obj) { throw null; } + public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? other) { throw null; } + public override bool Equals(object? obj) { throw null; } public override int GetHashCode() { throw null; } - public static bool operator ==(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource s1, Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource s2) { throw null; } - public static bool operator !=(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource s1, Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource s2) { throw null; } + public static bool operator ==(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? s1, Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? s2) { throw null; } + public static bool operator !=(Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? s1, Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? s2) { throw null; } } public partial class CompositeBindingSource : Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource { @@ -524,7 +525,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } public partial interface IBindingSourceMetadata { - Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource BindingSource { get; } + Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource? BindingSource { get; } } public partial interface IModelBinder { @@ -607,9 +608,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding private readonly object _dummy; private readonly int _dummyPrimitive; public bool IsModelSet { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public object Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public object? Model { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult other) { throw null; } - public override bool Equals(object obj) { throw null; } + public override bool Equals(object? obj) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult Failed() { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult x, Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult y) { throw null; } @@ -620,10 +621,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public partial class ModelError { public ModelError(System.Exception exception) { } - public ModelError(System.Exception exception, string errorMessage) { } - public ModelError(string errorMessage) { } + public ModelError(System.Exception exception, string? errorMessage) { } + public ModelError(string? errorMessage) { } public string ErrorMessage { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Exception? Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } public partial class ModelErrorCollection : System.Collections.ObjectModel.Collection { @@ -632,7 +633,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void Add(string errorMessage) { } } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerToString(),nq}")] - public abstract partial class ModelMetadata : Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider, System.IEquatable + public abstract partial class ModelMetadata : Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider, System.IEquatable { public static readonly int DefaultOrder; protected ModelMetadata(Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity identity) { } @@ -641,15 +642,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public abstract System.Type BinderType { get; } public abstract Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource BindingSource { get; } public virtual Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata ContainerMetadata { get { throw null; } } - public System.Type ContainerType { get { throw null; } } + public System.Type? ContainerType { get { throw null; } } public abstract bool ConvertEmptyStringToNull { get; } public abstract string DataTypeName { get; } public abstract string Description { get; } public abstract string DisplayFormatString { get; } public abstract string DisplayName { get; } public abstract string EditFormatString { get; } - public abstract Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata ElementMetadata { get; } - public System.Type ElementType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public abstract Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata? ElementMetadata { get; } + public System.Type? ElementType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public abstract System.Collections.Generic.IEnumerable> EnumGroupedDisplayNamesAndValues { get; } public abstract System.Collections.Generic.IReadOnlyDictionary EnumNamesAndValues { get; } public abstract bool HasNonDefaultEditFormat { get; } @@ -671,17 +672,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind MetadataKind { get { throw null; } } public abstract Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelBindingMessageProvider ModelBindingMessageProvider { get; } public System.Type ModelType { get { throw null; } } - public string Name { get { throw null; } } + public string? Name { get { throw null; } } public abstract string NullDisplayText { get; } public abstract int Order { get; } - public string ParameterName { get { throw null; } } + public string? ParameterName { get { throw null; } } public abstract string Placeholder { get; } public abstract Microsoft.AspNetCore.Mvc.ModelBinding.ModelPropertyCollection Properties { get; } public abstract Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider PropertyFilterProvider { get; } public abstract System.Func PropertyGetter { get; } - public string PropertyName { get { throw null; } } + public string? PropertyName { get { throw null; } } public abstract System.Action PropertySetter { get; } - public virtual Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter PropertyValidationFilter { get { throw null; } } + public virtual Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter? PropertyValidationFilter { get { throw null; } } public abstract bool ShowForDisplay { get; } public abstract bool ShowForEdit { get; } public abstract string SimpleDisplayProperty { get; } @@ -689,8 +690,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public System.Type UnderlyingOrModelType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public abstract bool ValidateChildren { get; } public abstract System.Collections.Generic.IReadOnlyList ValidatorMetadata { get; } - public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata other) { throw null; } - public override bool Equals(object obj) { throw null; } + public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata? other) { throw null; } + public override bool Equals(object? obj) { throw null; } public string GetDisplayName() { throw null; } public override int GetHashCode() { throw null; } public virtual System.Collections.Generic.IEnumerable GetMetadataForProperties(System.Type modelType) { throw null; } @@ -708,7 +709,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public partial class ModelPropertyCollection : System.Collections.ObjectModel.ReadOnlyCollection { public ModelPropertyCollection(System.Collections.Generic.IEnumerable properties) : base (default(System.Collections.Generic.IList)) { } - public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata this[string propertyName] { get { throw null; } } + public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata? this[string propertyName] { get { throw null; } } } public partial class ModelStateDictionary : System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyDictionary, System.Collections.IEnumerable { @@ -724,8 +725,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary.KeyEnumerable Keys { get { throw null; } } public int MaxAllowedErrors { get { throw null; } set { } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry Root { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState ValidationState { get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary.ValueEnumerable Values { get { throw null; } } public void AddModelError(string key, System.Exception exception, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata metadata) { } @@ -742,14 +743,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void Merge(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary dictionary) { } public bool Remove(string key) { throw null; } public void SetModelValue(string key, Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult valueProviderResult) { } - public void SetModelValue(string key, object rawValue, string attemptedValue) { } + public void SetModelValue(string key, object? rawValue, string attemptedValue) { } public static bool StartsWithPrefix(string prefix, string key) { throw null; } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryAddModelError(string key, System.Exception exception, Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata metadata) { throw null; } public bool TryAddModelError(string key, string errorMessage) { throw null; } public bool TryAddModelException(string key, System.Exception exception) { throw null; } - public bool TryGetValue(string key, out Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry value) { throw null; } + public bool TryGetValue(string key, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry? value) { throw null; } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { @@ -769,7 +770,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding private readonly int _dummyPrimitive; public KeyEnumerable(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary dictionary) { throw null; } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary.KeyEnumerator GetEnumerator() { throw null; } - System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] @@ -791,7 +792,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding private readonly int _dummyPrimitive; public PrefixEnumerable(Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary dictionary, string prefix) { throw null; } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary.Enumerator GetEnumerator() { throw null; } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] @@ -820,13 +821,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public abstract partial class ModelStateEntry { protected ModelStateEntry() { } - public string AttemptedValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract System.Collections.Generic.IReadOnlyList Children { get; } + public string? AttemptedValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public abstract System.Collections.Generic.IReadOnlyList? Children { get; } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelErrorCollection Errors { get { throw null; } } public abstract bool IsContainerNode { get; } - public object RawValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object? RawValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState ValidationState { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public abstract Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry GetModelStateForProperty(string propertyName); + public abstract Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry? GetModelStateForProperty(string propertyName); } public enum ModelValidationState { @@ -859,11 +860,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public ValueProviderResult(Microsoft.Extensions.Primitives.StringValues values) { throw null; } public ValueProviderResult(Microsoft.Extensions.Primitives.StringValues values, System.Globalization.CultureInfo culture) { throw null; } public System.Globalization.CultureInfo Culture { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public string FirstValue { get { throw null; } } + public string? FirstValue { get { throw null; } } public int Length { get { throw null; } } public Microsoft.Extensions.Primitives.StringValues Values { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult other) { throw null; } - public override bool Equals(object obj) { throw null; } + public override bool Equals(object? obj) { throw null; } public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult x, Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult y) { throw null; } @@ -896,14 +897,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { private readonly object _dummy; private readonly int _dummyPrimitive; - public System.Type ContainerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Type? ContainerType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind MetadataKind { get { throw null; } } public System.Type ModelType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public System.Reflection.ParameterInfo ParameterInfo { get { throw null; } } - public System.Reflection.PropertyInfo PropertyInfo { get { throw null; } } + public string? Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } + public System.Reflection.ParameterInfo? ParameterInfo { get { throw null; } } + public System.Reflection.PropertyInfo? PropertyInfo { get { throw null; } } public bool Equals(Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity other) { throw null; } - public override bool Equals(object obj) { throw null; } + public override bool Equals(object? obj) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForParameter(System.Reflection.ParameterInfo parameter, System.Type modelType) { throw null; } public static Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity ForProperty(System.Reflection.PropertyInfo propertyInfo, System.Type modelType, System.Type containerType) { throw null; } @@ -1000,17 +1001,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation public ValidationEntry(Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata metadata, string key, object model) { throw null; } public string Key { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata Metadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - public object Model { get { throw null; } } + public object? Model { get { throw null; } } } public partial class ValidationStateDictionary : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyDictionary, System.Collections.IEnumerable { public ValidationStateDictionary() { } public int Count { get { throw null; } } public bool IsReadOnly { get { throw null; } } - public Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry this[object key] { get { throw null; } set { } } + public Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry? this[object key] { get { throw null; } set { } } public System.Collections.Generic.ICollection Keys { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } - System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } + Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry System.Collections.Generic.IDictionary.this[object key] { get { throw null; } set { } } + Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry System.Collections.Generic.IReadOnlyDictionary.this[object key] { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } + System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } public System.Collections.Generic.ICollection Values { get { throw null; } } public void Add(System.Collections.Generic.KeyValuePair item) { } public void Add(object key, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry value) { } @@ -1022,7 +1025,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation public bool Remove(System.Collections.Generic.KeyValuePair item) { throw null; } public bool Remove(object key) { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(object key, out Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry value) { throw null; } + public bool TryGetValue(object key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry value) { throw null; } } public partial class ValidationStateEntry { @@ -1050,25 +1053,25 @@ namespace Microsoft.AspNetCore.Mvc.Routing public int Order { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool SuppressLinkGeneration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool SuppressPathMatching { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Template { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class UrlActionContext { public UrlActionContext() { } - public string Action { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Controller { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Fragment { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public object Values { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Action { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Controller { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Fragment { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object? Values { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } public partial class UrlRouteContext { public UrlRouteContext() { } - public string Fragment { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public string RouteName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } - public object Values { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Fragment { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Host { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public string? RouteName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } + public object? Values { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptor.cs b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptor.cs index c6200ba6ee..fe6130f382 100644 --- a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptor.cs +++ b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptor.cs @@ -38,42 +38,42 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// /// Gets or sets the . /// - public AttributeRouteInfo AttributeRouteInfo { get; set; } + public AttributeRouteInfo? AttributeRouteInfo { get; set; } /// /// The set of constraints for this action. Must all be satisfied for the action to be selected. /// - public IList ActionConstraints { get; set; } + public IList? ActionConstraints { get; set; } /// /// Gets or sets the endpoint metadata for this action. /// This API is meant for infrastructure and should not be used by application code. /// - public IList EndpointMetadata { get; set; } + public IList EndpointMetadata { get; set; } = Array.Empty(); /// /// The set of parameters associated with this action. /// - public IList Parameters { get; set; } + public IList Parameters { get; set; } = Array.Empty(); /// /// The set of properties which are model bound. /// - public IList BoundProperties { get; set; } + public IList BoundProperties { get; set; } = Array.Empty(); /// /// The set of filters associated with this action. /// - public IList FilterDescriptors { get; set; } + public IList FilterDescriptors { get; set; } = Array.Empty(); /// /// A friendly name for this action. /// - public virtual string DisplayName { get; set; } + public virtual string? DisplayName { get; set; } /// /// Stores arbitrary metadata properties associated with the . /// - public IDictionary Properties { get; set; } + public IDictionary Properties { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptorExtensions.cs b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptorExtensions.cs index 3cd94db1f1..e683080af6 100644 --- a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptorExtensions.cs +++ b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionDescriptorExtensions.cs @@ -24,14 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions throw new ArgumentNullException(nameof(actionDescriptor)); } - object value; - if (actionDescriptor.Properties.TryGetValue(typeof(T), out value)) + if (actionDescriptor.Properties.TryGetValue(typeof(T), out var value)) { return (T)value; } else { - return default(T); + return default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionInvokerProviderContext.cs b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionInvokerProviderContext.cs index 1356b02ea4..ccb5af263c 100644 --- a/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionInvokerProviderContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Abstractions/ActionInvokerProviderContext.cs @@ -32,6 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// /// Gets or sets the that will be used to invoke /// - public IActionInvoker Result { get; set; } + public IActionInvoker? Result { get; set; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Abstractions/ParameterDescriptor.cs b/src/Mvc/Mvc.Abstractions/src/Abstractions/ParameterDescriptor.cs index 33e3824a0b..4414affe32 100644 --- a/src/Mvc/Mvc.Abstractions/src/Abstractions/ParameterDescriptor.cs +++ b/src/Mvc/Mvc.Abstractions/src/Abstractions/ParameterDescriptor.cs @@ -14,16 +14,16 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// /// Gets or sets the parameter name. /// - public string Name { get; set; } + public string Name { get; set; } = default!; /// /// Gets or sets the type of the parameter. /// - public Type ParameterType { get; set; } + public Type ParameterType { get; set; } = default!; /// /// Gets or sets the for the parameter. /// - public BindingInfo BindingInfo { get; set; } + public BindingInfo BindingInfo { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintContext.cs b/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintContext.cs index 68ee228d0e..c475f0a634 100644 --- a/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintContext.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Routing; @@ -15,16 +16,16 @@ namespace Microsoft.AspNetCore.Mvc.ActionConstraints /// The list of . This includes all actions that are valid for the current /// request, as well as their constraints. /// - public IReadOnlyList Candidates { get; set; } + public IReadOnlyList Candidates { get; set; } = Array.Empty(); /// /// The current . /// - public ActionSelectorCandidate CurrentCandidate { get; set; } + public ActionSelectorCandidate CurrentCandidate { get; set; } = default!; /// /// The . /// - public RouteContext RouteContext { get; set; } + public RouteContext RouteContext { get; set; } = default!; } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintItem.cs b/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintItem.cs index 7022a8f283..8868005a57 100644 --- a/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintItem.cs +++ b/src/Mvc/Mvc.Abstractions/src/ActionConstraints/ActionConstraintItem.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ActionConstraints /// /// The associated with . /// - public IActionConstraint Constraint { get; set; } + public IActionConstraint Constraint { get; set; } = default!; /// /// The instance. diff --git a/src/Mvc/Mvc.Abstractions/src/ActionContext.cs b/src/Mvc/Mvc.Abstractions/src/ActionContext.cs index 744a439dc7..000e55558c 100644 --- a/src/Mvc/Mvc.Abstractions/src/ActionContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/ActionContext.cs @@ -31,10 +31,10 @@ namespace Microsoft.AspNetCore.Mvc /// The to copy. public ActionContext(ActionContext actionContext) : this( - actionContext?.HttpContext, - actionContext?.RouteData, - actionContext?.ActionDescriptor, - actionContext?.ModelState) + actionContext.HttpContext, + actionContext.RouteData, + actionContext.ActionDescriptor, + actionContext.ModelState) { } @@ -97,10 +97,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The property setter is provided for unit test purposes only. /// - public ActionDescriptor ActionDescriptor - { - get; set; - } + public ActionDescriptor ActionDescriptor { get; set; } = default!; /// /// Gets or sets the for the current request. @@ -108,18 +105,12 @@ namespace Microsoft.AspNetCore.Mvc /// /// The property setter is provided for unit test purposes only. /// - public HttpContext HttpContext - { - get; set; - } + public HttpContext HttpContext { get; set; } = default!; /// /// Gets the . /// - public ModelStateDictionary ModelState - { - get; - } + public ModelStateDictionary ModelState { get; } = default!; /// /// Gets or sets the for the current request. @@ -127,9 +118,6 @@ namespace Microsoft.AspNetCore.Mvc /// /// The property setter is provided for unit test purposes only. /// - public RouteData RouteData - { - get; set; - } + public RouteData RouteData { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs index 827712f1bc..f20437ce0d 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs @@ -16,17 +16,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Gets or sets for this api. /// - public ActionDescriptor ActionDescriptor { get; set; } + public ActionDescriptor ActionDescriptor { get; set; } = default!; /// /// Gets or sets group name for this api. /// - public string GroupName { get; set; } + public string? GroupName { get; set; } /// /// Gets or sets the supported HTTP method for this api, or null if all HTTP methods are supported. /// - public string HttpMethod { get; set; } + public string? HttpMethod { get; set; } /// /// Gets a list of for this api. @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Gets or sets relative url path template (relative to application root) for this api. /// - public string RelativePath { get; set; } + public string RelativePath { get; set; } = default!; /// /// Gets the list of possible formats for a request. diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterDescription.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterDescription.cs index 17f499c1f0..7b6d8f548a 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterDescription.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterDescription.cs @@ -15,37 +15,37 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Gets or sets the . /// - public ModelMetadata ModelMetadata { get; set; } + public ModelMetadata ModelMetadata { get; set; } = default!; /// /// Gets or sets the name. /// - public string Name { get; set; } + public string Name { get; set; } = default!; /// /// Gets or sets the . /// - public ApiParameterRouteInfo RouteInfo { get; set; } + public ApiParameterRouteInfo? RouteInfo { get; set; } /// /// Gets or sets the . /// - public BindingSource Source { get; set; } + public BindingSource Source { get; set; } = default!; /// /// Gets or sets the . /// - public BindingInfo BindingInfo { get; set; } + public BindingInfo? BindingInfo { get; set; } /// /// Gets or sets the parameter type. /// - public Type Type { get; set; } + public Type Type { get; set; } = default!; /// /// Gets or sets the parameter descriptor. /// - public ParameterDescriptor ParameterDescriptor { get; set; } + public ParameterDescriptor ParameterDescriptor { get; set; } = default!; /// /// Gets or sets a value that determines if the parameter is required. @@ -63,6 +63,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Gets or sets the default value for a parameter. /// - public object DefaultValue { get; set; } + public object? DefaultValue { get; set; } } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterRouteInfo.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterRouteInfo.cs index 6e5cbfc494..fbb79452d4 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterRouteInfo.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiParameterRouteInfo.cs @@ -18,12 +18,12 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// Route constraints are only applied when a value is bound from a URL's path. See /// for the data source considered. /// - public IEnumerable Constraints { get; set; } + public IEnumerable? Constraints { get; set; } /// /// Gets or sets the default value for the parameter. /// - public object DefaultValue { get; set; } + public object? DefaultValue { get; set; } /// /// Gets a value indicating whether not a parameter is considered optional by routing. diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiRequestFormat.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiRequestFormat.cs index 8d93cc65b3..3e063da212 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiRequestFormat.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiRequestFormat.cs @@ -13,11 +13,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// The formatter used to read this request. /// - public IInputFormatter Formatter { get; set; } + public IInputFormatter Formatter { get; set; } = default!; /// /// The media type of the request. /// - public string MediaType { get; set; } + public string MediaType { get; set; } = default!; } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseFormat.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseFormat.cs index bf04db9da7..9830ef279a 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseFormat.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseFormat.cs @@ -13,11 +13,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Gets or sets the formatter used to output this response. /// - public IOutputFormatter Formatter { get; set; } + public IOutputFormatter Formatter { get; set; } = default!; /// /// Gets or sets the media type of the response. /// - public string MediaType { get; set; } + public string MediaType { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseType.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseType.cs index 6ed9644931..1029ec3437 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseType.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseType.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// /// Will be null if is null or void. /// - public ModelMetadata ModelMetadata { get; set; } + public ModelMetadata? ModelMetadata { get; set; } /// /// Gets or sets the CLR data type of the response or null. @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// Microsoft.AspNetCore.Mvc.ProducesAttribute or Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute on an action method /// to specify a response type. /// - public Type Type { get; set; } + public Type? Type { get; set; } /// /// Gets or sets the HTTP response status code. diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutedContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutedContext.cs index 68e0343f04..6440a27878 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutedContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutedContext.cs @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// public class ActionExecutedContext : FilterContext { - private Exception _exception; - private ExceptionDispatchInfo _exceptionDispatchInfo; + private Exception? _exception; + private ExceptionDispatchInfo? _exceptionDispatchInfo; /// /// Instantiates a new instance. @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the caught while executing the action or action filters, if /// any. /// - public virtual Exception Exception + public virtual Exception? Exception { get { @@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the for the /// , if an was caught and this information captured. /// - public virtual ExceptionDispatchInfo ExceptionDispatchInfo + public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get { @@ -91,6 +91,6 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// /// Gets or sets the . /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult Result { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutingContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutingContext.cs index 65ade1e1c5..0bdd07c4a9 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutingContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ActionExecutingContext.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the to execute. Setting to a non-null /// value inside an action filter will short-circuit the action and any remaining action filters. /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult? Result { get; set; } /// /// Gets the arguments to pass when invoking the action. Keys are parameter names. diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/AuthorizationFilterContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/AuthorizationFilterContext.cs index a344d55432..0854a422d2 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/AuthorizationFilterContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/AuthorizationFilterContext.cs @@ -27,6 +27,6 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the result of the request. Setting to a non-null value inside /// an authorization filter will short-circuit the remainder of the filter pipeline. /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult? Result { get; set; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ExceptionContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ExceptionContext.cs index c0920f057b..d151134b8a 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ExceptionContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ExceptionContext.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// public class ExceptionContext : FilterContext { - private Exception _exception; - private ExceptionDispatchInfo _exceptionDispatchInfo; + private Exception? _exception; + private ExceptionDispatchInfo? _exceptionDispatchInfo; /// /// Instantiates a new instance. @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters } else { - return _exception; + return _exception!; } } @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the for the /// , if this information was captured. /// - public virtual ExceptionDispatchInfo ExceptionDispatchInfo + public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get { @@ -76,6 +76,6 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// /// Gets or sets the . /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult? Result { get; set; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs index 961e283399..97e0a294b6 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Filters { @@ -81,6 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// The implementation of applied to the action associated with /// the /// + [return: MaybeNull] public TMetadata FindEffectivePolicy() where TMetadata : IFilterMetadata { // The most specific policy is the one closest to the action (nearest the end of the list). @@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters } } - return default(TMetadata); + return default; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/FilterItem.cs b/src/Mvc/Mvc.Abstractions/src/Filters/FilterItem.cs index 516acd7d24..fdf03752b7 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/FilterItem.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/FilterItem.cs @@ -48,12 +48,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// /// Gets the containing the filter metadata. /// - public FilterDescriptor Descriptor { get; } + public FilterDescriptor Descriptor { get; } = default!; /// /// Gets or sets the executable associated with . /// - public IFilterMetadata Filter { get; set; } + public IFilterMetadata Filter { get; set; } = default!; /// /// Gets or sets a value indicating whether or not can be reused across requests. diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutedContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutedContext.cs index 6a54d2c410..8dfe78bba6 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutedContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutedContext.cs @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// public class ResourceExecutedContext : FilterContext { - private Exception _exception; - private ExceptionDispatchInfo _exceptionDispatchInfo; + private Exception? _exception; + private ExceptionDispatchInfo? _exceptionDispatchInfo; /// /// Creates a new . @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Setting to true will also mark the exception as handled. /// /// - public virtual Exception Exception + public virtual Exception? Exception { get { @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Setting to true will also mark the exception as handled. /// /// - public virtual ExceptionDispatchInfo ExceptionDispatchInfo + public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get { @@ -115,6 +115,6 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// to resource filters. /// /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult? Result { get; set; } } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutingContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutingContext.cs index 46378b3760..37985fc1af 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutingContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ResourceExecutingContext.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Setting to a non-null value inside a resource filter will /// short-circuit execution of additional resource filters and the action itself. /// - public virtual IActionResult Result { get; set; } + public virtual IActionResult? Result { get; set; } /// /// Gets the list of instances used by model binding. diff --git a/src/Mvc/Mvc.Abstractions/src/Filters/ResultExecutedContext.cs b/src/Mvc/Mvc.Abstractions/src/Filters/ResultExecutedContext.cs index ae4947d8bf..01eae1d8cf 100644 --- a/src/Mvc/Mvc.Abstractions/src/Filters/ResultExecutedContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Filters/ResultExecutedContext.cs @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// public class ResultExecutedContext : FilterContext { - private Exception _exception; - private ExceptionDispatchInfo _exceptionDispatchInfo; + private Exception? _exception; + private ExceptionDispatchInfo? _exceptionDispatchInfo; /// /// Instantiates a new instance. @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the caught while executing the result or result filters, if /// any. /// - public virtual Exception Exception + public virtual Exception? Exception { get { @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters /// Gets or sets the for the /// , if an was caught and this information captured. /// - public virtual ExceptionDispatchInfo ExceptionDispatchInfo + public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get { diff --git a/src/Mvc/Mvc.Abstractions/src/Formatters/FormatterCollection.cs b/src/Mvc/Mvc.Abstractions/src/Formatters/FormatterCollection.cs index 401febb5cf..ad16757da4 100644 --- a/src/Mvc/Mvc.Abstractions/src/Formatters/FormatterCollection.cs +++ b/src/Mvc/Mvc.Abstractions/src/Formatters/FormatterCollection.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// Represents a collection of formatters. /// /// The type of formatters in the collection. - public class FormatterCollection : Collection + public class FormatterCollection : Collection where TFormatter : notnull { /// /// Initializes a new instance of the class that is empty. diff --git a/src/Mvc/Mvc.Abstractions/src/Formatters/InputFormatterResult.cs b/src/Mvc/Mvc.Abstractions/src/Formatters/InputFormatterResult.cs index 2304cd5671..d83a5095d7 100644 --- a/src/Mvc/Mvc.Abstractions/src/Formatters/InputFormatterResult.cs +++ b/src/Mvc/Mvc.Abstractions/src/Formatters/InputFormatterResult.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// null if is true. /// - public object Model { get; } + public object? Model { get; } /// /// Returns an indicating the diff --git a/src/Mvc/Mvc.Abstractions/src/Formatters/OutputFormatterCanWriteContext.cs b/src/Mvc/Mvc.Abstractions/src/Formatters/OutputFormatterCanWriteContext.cs index ae48a91b1e..daf10315f0 100644 --- a/src/Mvc/Mvc.Abstractions/src/Formatters/OutputFormatterCanWriteContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Formatters/OutputFormatterCanWriteContext.cs @@ -53,11 +53,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// Gets or sets the object to write to the response. /// - public virtual object Object { get; protected set; } + public virtual object? Object { get; protected set; } /// /// Gets or sets the of the object to write to the response. /// - public virtual Type ObjectType { get; protected set; } + public virtual Type? ObjectType { get; protected set; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj index bc27d62e2c..b3e599ab25 100644 --- a/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -9,6 +9,7 @@ Microsoft.AspNetCore.Mvc.IActionResult true aspnetcore;aspnetcoremvc false + enable @@ -20,8 +21,4 @@ Microsoft.AspNetCore.Mvc.IActionResult - - - - diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingInfo.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingInfo.cs index 38fbc894a9..b7379ae4e0 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingInfo.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingInfo.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public class BindingInfo { - private Type _binderType; + private Type? _binderType; /// /// Creates a new . @@ -44,12 +44,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets or sets the . /// - public BindingSource BindingSource { get; set; } + public BindingSource? BindingSource { get; set; } /// /// Gets or sets the binder model name. /// - public string BinderModelName { get; set; } + public string? BinderModelName { get; set; } /// /// Gets or sets the of the implementation used to bind the @@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Also set if the specified implementation does not /// use values from form data, route values or the query string. /// - public Type BinderType + public Type? BinderType { get => _binderType; set @@ -80,13 +80,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets or sets the . /// - public IPropertyFilterProvider PropertyFilterProvider { get; set; } + public IPropertyFilterProvider? PropertyFilterProvider { get; set; } /// /// Gets or sets a predicate which determines whether or not the model should be bound based on state /// from the current request. /// - public Func RequestPredicate { get; set; } + public Func? RequestPredicate { get; set; } /// /// Gets or sets the value which decides if empty bodies are treated as valid inputs. @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A collection of attributes which are used to construct /// /// A new instance of . - public static BindingInfo GetBindingInfo(IEnumerable attributes) + public static BindingInfo? GetBindingInfo(IEnumerable attributes) { var bindingInfo = new BindingInfo(); var isBindingInfoPresent = false; @@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A collection of attributes which are used to construct . /// The . /// A new instance of if any binding metadata was discovered; otherwise or . - public static BindingInfo GetBindingInfo(IEnumerable attributes, ModelMetadata modelMetadata) + public static BindingInfo? GetBindingInfo(IEnumerable attributes, ModelMetadata modelMetadata) { if (attributes == null) { diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingSource.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingSource.cs index 97105efe3c..ea06036844 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingSource.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/BindingSource.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A metadata object representing a source of data for model binding. /// [DebuggerDisplay("Source: {DisplayName}")] - public class BindingSource : IEquatable + public class BindingSource : IEquatable { /// /// A for the request body. @@ -210,13 +210,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public bool Equals(BindingSource other) + public bool Equals(BindingSource? other) { return string.Equals(other?.Id, Id, StringComparison.Ordinal); } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return Equals(obj as BindingSource); } @@ -228,7 +228,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public static bool operator ==(BindingSource s1, BindingSource s2) + public static bool operator ==(BindingSource? s1, BindingSource? s2) { if (s1 is null) { @@ -239,7 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public static bool operator !=(BindingSource s1, BindingSource s2) + public static bool operator !=(BindingSource? s1, BindingSource? s2) { return !(s1 == s2); } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/CompositeBindingSource.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/CompositeBindingSource.cs index 886deb9951..99044400b5 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/CompositeBindingSource.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/CompositeBindingSource.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public override bool CanAcceptDataFrom(BindingSource bindingSource) { - if (bindingSource == null) + if (bindingSource is null) { throw new ArgumentNullException(nameof(bindingSource)); } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IBindingSourceMetadata.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IBindingSourceMetadata.cs index 4a1ed31275..8639ce826e 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/IBindingSourceMetadata.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/IBindingSourceMetadata.cs @@ -15,6 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// The is metadata which can be used to determine which data /// sources are valid for model binding of a property or parameter. /// - BindingSource BindingSource { get; } + BindingSource? BindingSource { get; } } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelBindingMessageProvider.cs index c91bfe4cb0..7992450f37 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelBindingMessageProvider.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelBindingMessageProvider.cs @@ -17,28 +17,28 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// /// Default is "A value for the '{0}' parameter or property was not provided.". /// - public virtual Func MissingBindRequiredValueAccessor { get; } + public virtual Func MissingBindRequiredValueAccessor { get; } = default!; /// /// Error message the model binding system adds when either the key or the value of a /// is bound but not both. /// /// Default is "A value is required.". - public virtual Func MissingKeyOrValueAccessor { get; } + public virtual Func MissingKeyOrValueAccessor { get; } = default!; /// /// Error message the model binding system adds when no value is provided for the request body, /// but a value is required. /// /// Default is "A non-empty request body is required.". - public virtual Func MissingRequestBodyRequiredValueAccessor { get; } + public virtual Func MissingRequestBodyRequiredValueAccessor { get; } = default!; /// /// Error message the model binding system adds when a null value is bound to a /// non- property. /// /// Default is "The value '{0}' is invalid.". - public virtual Func ValueMustNotBeNullAccessor { get; } + public virtual Func ValueMustNotBeNullAccessor { get; } = default!; /// /// Error message the model binding system adds when is of type @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// with a property. /// /// Default is "The value '{0}' is not valid for {1}.". - public virtual Func AttemptedValueIsInvalidAccessor { get; } + public virtual Func AttemptedValueIsInvalidAccessor { get; } = default!; /// /// Error message the model binding system adds when is of type @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// with a collection element or action parameter. /// /// Default is "The value '{0}' is not valid.". - public virtual Func NonPropertyAttemptedValueIsInvalidAccessor { get; } + public virtual Func NonPropertyAttemptedValueIsInvalidAccessor { get; } = default!; /// /// Error message the model binding system adds when is of type @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// with a property. /// /// Default is "The supplied value is invalid for {0}.". - public virtual Func UnknownValueIsInvalidAccessor { get; } + public virtual Func UnknownValueIsInvalidAccessor { get; } = default!; /// /// Error message the model binding system adds when is of type @@ -70,21 +70,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// with a collection element or action parameter. /// /// Default is "The supplied value is invalid.". - public virtual Func NonPropertyUnknownValueIsInvalidAccessor { get; } + public virtual Func NonPropertyUnknownValueIsInvalidAccessor { get; } = default!; /// /// Fallback error message HTML and tag helpers display when a property is invalid but the /// s have null s. /// /// Default is "The value '{0}' is invalid.". - public virtual Func ValueIsInvalidAccessor { get; } + public virtual Func ValueIsInvalidAccessor { get; } = default!; /// /// Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the /// browser if the field for a float (for example) property does not have a correctly-formatted value. /// /// Default is "The field {0} must be a number.". - public virtual Func ValueMustBeANumberAccessor { get; } + public virtual Func ValueMustBeANumberAccessor { get; } = default!; /// /// Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the @@ -92,6 +92,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// correctly-formatted value. /// /// Default is "The field must be a number.". - public virtual Func NonPropertyValueMustBeANumberAccessor { get; } + public virtual Func NonPropertyValueMustBeANumberAccessor { get; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelMetadataIdentity.cs index 9b1cf4fec2..5fff9bad3d 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelMetadataIdentity.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -4,7 +4,6 @@ using System; using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { @@ -15,9 +14,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { private ModelMetadataIdentity( Type modelType, - string name = null, - Type containerType = null, - object fieldInfo = null) + string? name = null, + Type? containerType = null, + object? fieldInfo = null) { ModelType = modelType; Name = name; @@ -107,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// The . /// A . public static ModelMetadataIdentity ForParameter(ParameterInfo parameter) - => ForParameter(parameter, parameter?.ParameterType); + => ForParameter(parameter, parameter.ParameterType); /// /// Creates a for the provided parameter with the specified @@ -135,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// Gets the defining the model property represented by the current /// instance, or null if the current instance does not represent a property. /// - public Type ContainerType { get; } + public Type? ContainerType { get; } /// /// Gets the represented by the current instance. @@ -168,21 +167,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// Gets the name of the current instance if it represents a parameter or property, or null if /// the current instance represents a type. /// - public string Name { get; } + public string? Name { get; } - private object FieldInfo { get; } + private object? FieldInfo { get; } /// /// Gets a descriptor for the parameter, or null if this instance /// does not represent a parameter. /// - public ParameterInfo ParameterInfo => FieldInfo as ParameterInfo; + public ParameterInfo? ParameterInfo => FieldInfo as ParameterInfo; /// /// Gets a descriptor for the property, or null if this instance /// does not represent a property. /// - public PropertyInfo PropertyInfo => FieldInfo as PropertyInfo; + public PropertyInfo? PropertyInfo => FieldInfo as PropertyInfo; /// public bool Equals(ModelMetadataIdentity other) @@ -196,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as ModelMetadataIdentity?; return other.HasValue && Equals(other.Value); @@ -205,13 +204,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// public override int GetHashCode() { - var hash = new HashCodeCombiner(); + var hash = new HashCode(); hash.Add(ContainerType); hash.Add(ModelType); hash.Add(Name, StringComparer.Ordinal); hash.Add(ParameterInfo); hash.Add(PropertyInfo); - return hash; + return hash.ToHashCode(); } } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBinderProviderContext.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBinderProviderContext.cs index 6228923e70..c35c8c4814 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBinderProviderContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBinderProviderContext.cs @@ -48,6 +48,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets the . /// - public virtual IServiceProvider Services { get; } + public virtual IServiceProvider Services { get; } = default!; } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingContext.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingContext.cs index 915ea38085..3dfd4790f3 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingContext.cs @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets the associated with this context. /// - public virtual HttpContext HttpContext => ActionContext?.HttpContext; + public virtual HttpContext HttpContext => ActionContext?.HttpContext!; /// /// Gets or sets an indication that the current binder is handling the top-level object. @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Gets or sets the name of the top-level model. This is not reset to when value /// providers have no match for that model. /// - public string OriginalModelName { get; protected set; } + public string OriginalModelName { get; protected set; } = default!; /// /// Gets or sets the used to capture values diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingResult.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingResult.cs index 2bfc3dd5fd..3ab9c0ba4f 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingResult.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelBindingResult.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ModelBinding { @@ -19,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { return new ModelBindingResult(model: null, isModelSet: false); } - + /// /// Creates a representing a successful model binding operation. /// @@ -27,10 +26,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A representing a successful model bind. public static ModelBindingResult Success(object model) { - return new ModelBindingResult( model, isModelSet: true); + return new ModelBindingResult(model, isModelSet: true); } - private ModelBindingResult(object model, bool isModelSet) + private ModelBindingResult(object? model, bool isModelSet) { Model = model; IsModelSet = isModelSet; @@ -39,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets the model associated with this context. /// - public object Model { get; } + public object? Model { get; } /// /// @@ -53,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public bool IsModelSet { get; } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as ModelBindingResult?; if (other == null) @@ -69,11 +68,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public override int GetHashCode() { - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(IsModelSet); hashCodeCombiner.Add(Model); - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } /// diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelError.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelError.cs index 0473e20d5d..d30796e081 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelError.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelError.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// The . /// The error message. - public ModelError(Exception exception, string errorMessage) + public ModelError(Exception exception, string? errorMessage) : this(errorMessage) { Exception = exception ?? throw new ArgumentNullException(nameof(exception)); @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Initializes a new instance of with the specified . /// /// The error message. - public ModelError(string errorMessage) + public ModelError(string? errorMessage) { ErrorMessage = errorMessage ?? string.Empty; } @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets the associated with this instance. /// - public Exception Exception { get; } + public Exception? Exception { get; } /// /// Gets the error message associated with this instance. diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs index 4733f732cd..f04cac166e 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// A metadata representation of a model type, property or parameter. /// [DebuggerDisplay("{DebuggerToString(),nq}")] - public abstract class ModelMetadata : IEquatable, IModelMetadataProvider + public abstract class ModelMetadata : IEquatable, IModelMetadataProvider { /// /// The default value of . @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Gets the type containing the property if this metadata is for a property; otherwise. /// - public Type ContainerType => Identity.ContainerType; + public Type? ContainerType => Identity.ContainerType; /// /// Gets the metadata for if this metadata is for a property; @@ -68,17 +68,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Gets the name of the parameter or property if this metadata is for a parameter or property; /// otherwise i.e. if this is the metadata for a type. /// - public string Name => Identity.Name; + public string? Name => Identity.Name; /// /// Gets the name of the parameter if this metadata is for a parameter; otherwise. /// - public string ParameterName => MetadataKind == ModelMetadataKind.Parameter ? Identity.Name : null; + public string? ParameterName => MetadataKind == ModelMetadataKind.Parameter ? Identity.Name : null; /// /// Gets the name of the property if this metadata is for a property; otherwise. /// - public string PropertyName => MetadataKind == ModelMetadataKind.Property ? Identity.Name : null; + public string? PropertyName => MetadataKind == ModelMetadataKind.Property ? Identity.Name : null; /// /// Gets the key for the current instance. @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// implements but not . null otherwise i.e. when /// is false. /// - public abstract ModelMetadata ElementMetadata { get; } + public abstract ModelMetadata? ElementMetadata { get; } /// /// Gets the ordered and grouped display names and values of all values in @@ -318,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// validated. If null, properties with this are validated. /// /// Defaults to null. - public virtual IPropertyValidationFilter PropertyValidationFilter => null; + public virtual IPropertyValidationFilter? PropertyValidationFilter => null; /// /// Gets a value that indicates whether properties or elements of the model should be validated. @@ -343,7 +343,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// Gets the for elements of if that /// implements . /// - public Type ElementType { get; private set; } + public Type? ElementType { get; private set; } /// /// Gets a value indicating whether is a complex type. @@ -389,7 +389,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Identical to unless is true. /// - public Type UnderlyingOrModelType { get; private set; } + public Type UnderlyingOrModelType { get; private set; } = default!; /// /// Gets a property getter delegate to get the property value from a model object. @@ -415,7 +415,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public bool Equals(ModelMetadata other) + public bool Equals(ModelMetadata? other) { if (object.ReferenceEquals(this, other)) { @@ -433,7 +433,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return Equals(obj as ModelMetadata); } @@ -469,14 +469,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding else if (ModelType.IsArray) { IsEnumerableType = true; - ElementType = ModelType.GetElementType(); + ElementType = ModelType.GetElementType()!; } else { IsEnumerableType = true; var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(IEnumerable<>)); - ElementType = enumerableType?.GenericTypeArguments[0]; + ElementType = enumerableType?.GenericTypeArguments[0]!; if (ElementType == null) { @@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding case ModelMetadataKind.Parameter: return $"ModelMetadata (Parameter: '{ParameterName}' Type: '{ModelType.Name}')"; case ModelMetadataKind.Property: - return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')"; + return $"ModelMetadata (Property: '{ContainerType!.Name}.{PropertyName}' Type: '{ModelType.Name}')"; case ModelMetadataKind.Type: return $"ModelMetadata (Type: '{ModelType.Name}')"; default: diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelPropertyCollection.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelPropertyCollection.cs index 733da709ba..797e1b9514 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelPropertyCollection.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelPropertyCollection.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// The instance for the property specified by , or /// null if no match can be found. /// - public ModelMetadata this[string propertyName] + public ModelMetadata? this[string propertyName] { get { diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateDictionary.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateDictionary.cs index 48b8b9ee3a..24581e0510 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateDictionary.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateDictionary.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Formatters; @@ -166,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } TryGetValue(key, out var entry); - return entry; + return entry!; } } @@ -305,15 +306,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } else if (entry == null) { - errorMessage = messageProvider.UnknownValueIsInvalidAccessor(name); + errorMessage = messageProvider.UnknownValueIsInvalidAccessor(name!); } else if (name == null) { - errorMessage = messageProvider.NonPropertyAttemptedValueIsInvalidAccessor(entry.AttemptedValue); + errorMessage = messageProvider.NonPropertyAttemptedValueIsInvalidAccessor(entry.AttemptedValue!); } else { - errorMessage = messageProvider.AttemptedValueIsInvalidAccessor(entry.AttemptedValue, name); + errorMessage = messageProvider.AttemptedValueIsInvalidAccessor(entry.AttemptedValue!, name); } return TryAddModelError(key, errorMessage); @@ -511,7 +512,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// The values of in a comma-separated . /// - public void SetModelValue(string key, object rawValue, string attemptedValue) + public void SetModelValue(string key, object? rawValue, string attemptedValue) { if (key == null) { @@ -540,7 +541,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } // Avoid creating a new array for rawValue if there's only one value. - object rawValue; + object? rawValue; if (valueProviderResult == ValueProviderResult.None) { rawValue = null; @@ -573,10 +574,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } } - private ModelStateNode GetNode(string key) + private ModelStateNode? GetNode(string key) { - Debug.Assert(key != null); - var current = _root; if (key.Length > 0) { @@ -661,7 +660,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return new StringSegment(key, keyStart, index - keyStart); } - private static ModelValidationState? GetValidity(ModelStateNode node) + private static ModelValidationState? GetValidity(ModelStateNode? node) { if (node == null) { @@ -774,7 +773,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - public bool TryGetValue(string key, out ModelStateEntry value) + public bool TryGetValue(string key, [NotNullWhen(true)] out ModelStateEntry? value) { if (key == null) { @@ -893,11 +892,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding SubKey = subKey; } - public List ChildNodes { get; set; } + public List? ChildNodes { get; set; } - public override IReadOnlyList Children => ChildNodes; + public override IReadOnlyList? Children => ChildNodes; - public string Key { get; set; } + public string Key { get; set; } = default!; public StringSegment SubKey { get; } @@ -931,9 +930,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ModelStateNode GetNode(StringSegment subKey) + public ModelStateNode? GetNode(StringSegment subKey) { - ModelStateNode modelStateNode = null; + ModelStateNode? modelStateNode = null; if (subKey.Length == 0) { modelStateNode = this; @@ -981,7 +980,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return modelStateNode; } - public override ModelStateEntry GetModelStateForProperty(string propertyName) + public override ModelStateEntry? GetModelStateForProperty(string propertyName) => GetNode(new StringSegment(propertyName)); private int BinarySearch(StringSegment searchKey) @@ -1068,9 +1067,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public struct Enumerator : IEnumerator> { - private readonly ModelStateNode _rootNode; + private readonly ModelStateNode? _rootNode; private ModelStateNode _modelStateNode; - private List _nodes; + private List? _nodes; private int _index; private bool _visitedRoot; @@ -1093,7 +1092,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding _index = -1; _rootNode = dictionary.GetNode(prefix); - _modelStateNode = null; + _modelStateNode = default!; _nodes = null; _visitedRoot = false; } @@ -1141,7 +1140,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding while (_nodes.Count > 0) { var node = _nodes[0]; - if (_index == node.ChildNodes.Count - 1) + if (_index == node.ChildNodes!.Count - 1) { // We've exhausted the current sublist. _nodes.RemoveAt(0); @@ -1173,9 +1172,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void Reset() { _index = -1; - _nodes.Clear(); + _nodes?.Clear(); _visitedRoot = false; - _modelStateNode = null; + _modelStateNode = default!; } } @@ -1218,7 +1217,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public KeyEnumerator(ModelStateDictionary dictionary, string prefix) { _prefixEnumerator = new Enumerator(dictionary, prefix); - Current = null; + Current = default!; } /// @@ -1240,7 +1239,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } else { - Current = null; + Current = default!; } return result; @@ -1250,7 +1249,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void Reset() { _prefixEnumerator.Reset(); - Current = null; + Current = default!; } } @@ -1293,7 +1292,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public ValueEnumerator(ModelStateDictionary dictionary, string prefix) { _prefixEnumerator = new Enumerator(dictionary, prefix); - Current = null; + Current = default!; } /// @@ -1315,7 +1314,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } else { - Current = null; + Current = default!; } return result; @@ -1325,7 +1324,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void Reset() { _prefixEnumerator.Reset(); - Current = null; + Current = default!; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateEntry.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateEntry.cs index b73eab4f77..d56ef3dd81 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateEntry.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateEntry.cs @@ -10,17 +10,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public abstract class ModelStateEntry { - private ModelErrorCollection _errors; + private ModelErrorCollection? _errors; /// /// Gets the raw value from the request associated with this entry. /// - public object RawValue { get; set; } + public object? RawValue { get; set; } /// /// Gets the set of values contained in , joined into a comma-separated string. /// - public string AttemptedValue { get; set; } + public string? AttemptedValue { get; set; } /// /// Gets the for this entry. @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// This method returns any existing entry, even those with with value /// . /// - public abstract ModelStateEntry GetModelStateForProperty(string propertyName); + public abstract ModelStateEntry? GetModelStateForProperty(string propertyName); /// /// Gets the values for sub-properties. @@ -70,6 +70,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// This property returns all existing entries, even those with with value /// . /// - public abstract IReadOnlyList Children { get; } + public abstract IReadOnlyList? Children { get; } } } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ClientValidatorItem.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ClientValidatorItem.cs index e346509701..be8552d8df 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ClientValidatorItem.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ClientValidatorItem.cs @@ -30,12 +30,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// /// Gets the metadata associated with the . /// - public object ValidatorMetadata { get; } + public object ValidatorMetadata { get; } = default!; /// /// Gets or sets the . /// - public IClientModelValidator Validator { get; set; } + public IClientModelValidator Validator { get; set; } = default!; /// /// Gets or sets a value indicating whether or not can be reused across requests. diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationEntry.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationEntry.cs index a873273ca7..cedca096a4 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationEntry.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationEntry.cs @@ -10,8 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// public struct ValidationEntry { - private object _model; - private Func _modelAccessor; + private object? _model; + private Func? _modelAccessor; /// /// Creates a new . @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// /// The model object. /// - public object Model + public object? Model { get { diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateDictionary.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateDictionary.cs index 11179b014d..2d40811d5c 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateDictionary.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateDictionary.cs @@ -4,7 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { @@ -25,8 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation _inner = new Dictionary(ReferenceEqualityComparer.Instance); } - /// - public ValidationStateEntry this[object key] + public ValidationStateEntry? this[object key] { get { @@ -36,10 +35,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation set { - _inner[key] = value; + _inner[key] = value!; } } + /// + ValidationStateEntry IDictionary.this[object key] + { + get => this[key]!; + set => this[key] = value; + } + + ValidationStateEntry IReadOnlyDictionary.this[object key] => this[key]!; + /// public int Count => _inner.Count; @@ -115,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation } /// - public bool TryGetValue(object key, out ValidationStateEntry value) + public bool TryGetValue(object key, [MaybeNullWhen(false)] out ValidationStateEntry value) { return _inner.TryGetValue(key, out value); } @@ -125,30 +133,5 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { return ((IDictionary)_inner).GetEnumerator(); } - - private class ReferenceEqualityComparer : IEqualityComparer - { - private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; - - public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); - - public new bool Equals(object x, object y) - { - return Object.ReferenceEquals(x, y); - } - - public int GetHashCode(object obj) - { - // RuntimeHelpers.GetHashCode sometimes crashes the runtime on Mono 4.0.4 - // See: https://github.com/aspnet/External/issues/45 - // The workaround here is to just not hash anything, and fall back to an equality check. - if (IsMono) - { - return 0; - } - - return RuntimeHelpers.GetHashCode(obj); - } - } } } \ No newline at end of file diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateEntry.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateEntry.cs index 970cbb8dfa..ee94334283 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateEntry.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidationStateEntry.cs @@ -12,12 +12,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// /// Gets or sets the model prefix associated with the entry. /// - public string Key { get; set; } + public string Key { get; set; } = default!; /// /// Gets or sets the associated with the entry. /// - public ModelMetadata Metadata { get; set; } + public ModelMetadata Metadata { get; set; } = default!; /// /// Gets or sets a value indicating whether the associated model object should be validated. @@ -28,6 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// Gets or sets an for enumerating child entries of the associated /// model object. /// - public IValidationStrategy Strategy { get; set; } + public IValidationStrategy Strategy { get; set; } = default!; } } diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidatorItem.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidatorItem.cs index 8592eeca71..ca8b291e1a 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidatorItem.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/Validation/ValidatorItem.cs @@ -30,12 +30,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// /// Gets the metadata associated with the . /// - public object ValidatorMetadata { get; } + public object ValidatorMetadata { get; } = default!; /// /// Gets or sets the . /// - public IModelValidator Validator { get; set; } + public IModelValidator Validator { get; set; } = default!; /// /// Gets or sets a value indicating whether or not can be reused across requests. diff --git a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ValueProviderResult.cs b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ValueProviderResult.cs index 3a0b8e1fa8..e5b088b4a0 100644 --- a/src/Mvc/Mvc.Abstractions/src/ModelBinding/ValueProviderResult.cs +++ b/src/Mvc/Mvc.Abstractions/src/ModelBinding/ValueProviderResult.cs @@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// to get a single value for processing regardless of whether a single or multiple values were provided /// in the request. /// - public string FirstValue + public string? FirstValue { get { @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public int Length => Values.Count; /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as ValueProviderResult?; return other.HasValue && Equals(other.Value); diff --git a/src/Mvc/Mvc.Abstractions/src/Routing/AttributeRouteInfo.cs b/src/Mvc/Mvc.Abstractions/src/Routing/AttributeRouteInfo.cs index f2fc4ea77b..1aae06847e 100644 --- a/src/Mvc/Mvc.Abstractions/src/Routing/AttributeRouteInfo.cs +++ b/src/Mvc/Mvc.Abstractions/src/Routing/AttributeRouteInfo.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The route template. May be null if the action has no attribute routes. /// - public string Template { get; set; } + public string? Template { get; set; } /// /// Gets the order of the route associated with a given action. This property determines @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// to generate a link by referring to the route by name instead of attempting to match a /// route by provided route data. /// - public string Name { get; set; } + public string Name { get; set; } = default!; /// /// Gets or sets a value that determines if the route entry associated with this model participates in link generation. diff --git a/src/Mvc/Mvc.Abstractions/src/Routing/UrlActionContext.cs b/src/Mvc/Mvc.Abstractions/src/Routing/UrlActionContext.cs index 8260b6b680..09853000d0 100644 --- a/src/Mvc/Mvc.Abstractions/src/Routing/UrlActionContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Routing/UrlActionContext.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The name of the action method that uses to generate URLs. /// - public string Action + public string? Action { get; set; @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The name of the controller that uses to generate URLs. /// - public string Controller + public string? Controller { get; set; @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The object that contains the route values that /// uses to generate URLs. /// - public object Values + public object? Values { get; set; @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The protocol for the URLs that generates, /// such as "http" or "https" /// - public string Protocol + public string? Protocol { get; set; @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The host name for the URLs that generates. /// - public string Host + public string? Host { get; set; @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The fragment for the URLs that generates. /// - public string Fragment + public string? Fragment { get; set; diff --git a/src/Mvc/Mvc.Abstractions/src/Routing/UrlRouteContext.cs b/src/Mvc/Mvc.Abstractions/src/Routing/UrlRouteContext.cs index 706414f958..d713e69212 100644 --- a/src/Mvc/Mvc.Abstractions/src/Routing/UrlRouteContext.cs +++ b/src/Mvc/Mvc.Abstractions/src/Routing/UrlRouteContext.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The name of the route that uses to generate URLs. /// - public string RouteName + public string? RouteName { get; set; @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The object that contains the route values that /// uses to generate URLs. /// - public object Values + public object? Values { get; set; @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The protocol for the URLs that generates, /// such as "http" or "https" /// - public string Protocol + public string? Protocol { get; set; @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The host name for the URLs that generates. /// - public string Host + public string? Host { get; set; @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The fragment for the URLs that generates. /// - public string Fragment + public string? Fragment { get; set; diff --git a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs index bbc1f86ef1..73a4c35492 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs @@ -662,11 +662,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public int GetHashCode(PropertyKey obj) { - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(obj.ContainerType); hashCodeCombiner.Add(obj.PropertyName); hashCodeCombiner.Add(obj.Source); - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.Core/src/ModelBinding/ModelBinderFactory.cs b/src/Mvc/Mvc.Core/src/ModelBinding/ModelBinderFactory.cs index 1cd59d4c72..180fb433e1 100644 --- a/src/Mvc/Mvc.Core/src/ModelBinding/ModelBinderFactory.cs +++ b/src/Mvc/Mvc.Core/src/ModelBinding/ModelBinderFactory.cs @@ -319,10 +319,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public override int GetHashCode() { - var hash = new HashCodeCombiner(); + var hash = new HashCode(); hash.Add(_metadata); hash.Add(RuntimeHelpers.GetHashCode(_token)); - return hash; + return hash.ToHashCode(); } public override string ToString() diff --git a/src/Mvc/Mvc.Core/src/Routing/AttributeRoute.cs b/src/Mvc/Mvc.Core/src/Routing/AttributeRoute.cs index da0fc4594e..185e77fb58 100644 --- a/src/Mvc/Mvc.Core/src/Routing/AttributeRoute.cs +++ b/src/Mvc/Mvc.Core/src/Routing/AttributeRoute.cs @@ -291,10 +291,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing return 0; } - var hash = new HashCodeCombiner(); + var hash = new HashCode(); hash.Add(obj.Order); hash.Add(obj.RouteTemplate.TemplateText, StringComparer.OrdinalIgnoreCase); - return hash; + return hash.ToHashCode(); } } diff --git a/src/Mvc/Mvc.Core/test/ContentResultTest.cs b/src/Mvc/Mvc.Core/test/ContentResultTest.cs index a9b044cb2c..afe9675a69 100644 --- a/src/Mvc/Mvc.Core/test/ContentResultTest.cs +++ b/src/Mvc/Mvc.Core/test/ContentResultTest.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc Content = null, ContentType = new MediaTypeHeaderValue("text/plain") { - Encoding = Encoding.UTF7 + Encoding = Encoding.Unicode }.ToString() }; var httpContext = GetHttpContext(); @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc await contentResult.ExecuteResultAsync(actionContext); // Assert - MediaTypeAssert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType); + MediaTypeAssert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType); } public static TheoryData ContentResultContentTypeData diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs index e6243fcb45..c0d929ecea 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs @@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (fileBufferingWriteStream != null) { response.ContentLength = fileBufferingWriteStream.Length; - await fileBufferingWriteStream.DrainBufferAsync(response.Body); + await fileBufferingWriteStream.DrainBufferAsync(response.BodyWriter); } } finally diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs index e98d4477bd..d184f54b21 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs @@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (fileBufferingWriteStream != null) { response.ContentLength = fileBufferingWriteStream.Length; - await fileBufferingWriteStream.DrainBufferAsync(response.Body); + await fileBufferingWriteStream.DrainBufferAsync(response.BodyWriter); } } finally diff --git a/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs b/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs index 9acc858e5a..49eb5c05a9 100644 --- a/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs +++ b/src/Mvc/Mvc.Razor/src/RazorPageActivator.cs @@ -123,14 +123,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor public override int GetHashCode() { - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(PageType); if (ProvidedModelType != null) { hashCodeCombiner.Add(ProvidedModelType); } - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.Razor/src/ViewLocationCacheKey.cs b/src/Mvc/Mvc.Razor/src/ViewLocationCacheKey.cs index 2640110dd5..140b55ba9e 100644 --- a/src/Mvc/Mvc.Razor/src/ViewLocationCacheKey.cs +++ b/src/Mvc/Mvc.Razor/src/ViewLocationCacheKey.cs @@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// public override int GetHashCode() { - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(IsMainPage ? 1 : 0); hashCodeCombiner.Add(ViewName, StringComparer.Ordinal); hashCodeCombiner.Add(ControllerName, StringComparer.Ordinal); @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor } } - return hashCodeCombiner; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.TagHelpers/src/Cache/CacheTagKey.cs b/src/Mvc/Mvc.TagHelpers/src/Cache/CacheTagKey.cs index bf327f30ca..f527f8f55f 100644 --- a/src/Mvc/Mvc.TagHelpers/src/Cache/CacheTagKey.cs +++ b/src/Mvc/Mvc.TagHelpers/src/Cache/CacheTagKey.cs @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache return _hashcode.Value; } - var hashCodeCombiner = new HashCodeCombiner(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(Key, StringComparer.Ordinal); hashCodeCombiner.Add(_expiresAfter); @@ -258,12 +258,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache hashCodeCombiner.Add(_requestCulture); hashCodeCombiner.Add(_requestUICulture); - CombineCollectionHashCode(hashCodeCombiner, VaryByCookieName, _cookies); - CombineCollectionHashCode(hashCodeCombiner, VaryByHeaderName, _headers); - CombineCollectionHashCode(hashCodeCombiner, VaryByQueryName, _queries); - CombineCollectionHashCode(hashCodeCombiner, VaryByRouteName, _routeValues); + CombineCollectionHashCode(ref hashCodeCombiner, VaryByCookieName, _cookies); + CombineCollectionHashCode(ref hashCodeCombiner, VaryByHeaderName, _headers); + CombineCollectionHashCode(ref hashCodeCombiner, VaryByQueryName, _queries); + CombineCollectionHashCode(ref hashCodeCombiner, VaryByRouteName, _routeValues); - _hashcode = hashCodeCombiner.CombinedHash; + _hashcode = hashCodeCombiner.ToHashCode(); return _hashcode.Value; } @@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache } private static void CombineCollectionHashCode( - HashCodeCombiner hashCodeCombiner, + ref HashCode hashCodeCombiner, string collectionName, IList> values) { diff --git a/src/Mvc/Mvc.TagHelpers/src/GlobbingUrlBuilder.cs b/src/Mvc/Mvc.TagHelpers/src/GlobbingUrlBuilder.cs index 82c176bc8b..e0a04a5374 100644 --- a/src/Mvc/Mvc.TagHelpers/src/GlobbingUrlBuilder.cs +++ b/src/Mvc/Mvc.TagHelpers/src/GlobbingUrlBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Security.Policy; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; @@ -373,11 +374,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public override int GetHashCode() { - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(Include); hashCodeCombiner.Add(Exclude); - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.TagHelpers/test/CacheTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/CacheTagHelperTest.cs index 90313c0efa..525f1107f2 100644 --- a/src/Mvc/Mvc.TagHelpers/test/CacheTagHelperTest.cs +++ b/src/Mvc/Mvc.TagHelpers/test/CacheTagHelperTest.cs @@ -887,7 +887,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Fact] - [QuarantinedTest] public async Task ProcessAsync_AwaitersUseTheResultOfExecutor() { // Arrange 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 62cfeb38a0..b6c137cfea 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 @@ -652,6 +652,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerToString()}")] public partial class TagBuilder : Microsoft.AspNetCore.Html.IHtmlContent { + public TagBuilder(Microsoft.AspNetCore.Mvc.Rendering.TagBuilder tagBuilder) { } public TagBuilder(string tagName) { } public Microsoft.AspNetCore.Mvc.ViewFeatures.AttributeDictionary Attributes { get { throw null; } } public bool HasInnerHtml { get { throw null; } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/LambdaExpressionComparer.cs b/src/Mvc/Mvc.ViewFeatures/src/LambdaExpressionComparer.cs index 1e0dcc7fe7..e3448c15c0 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/LambdaExpressionComparer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/LambdaExpressionComparer.cs @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public int GetHashCode(LambdaExpression lambdaExpression) { var expression = lambdaExpression.Body; - var hashCodeCombiner = HashCodeCombiner.Start(); + var hashCodeCombiner = new HashCode(); while (true) { @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } } - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKeyComparer.cs b/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKeyComparer.cs index 5911611c89..b1586e71ba 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKeyComparer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKeyComparer.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using Microsoft.Extensions.Internal; @@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public int GetHashCode(MemberExpressionCacheKey obj) { - var hashCodeCombiner = new HashCodeCombiner(); + var hashCodeCombiner = new HashCode(); hashCodeCombiner.Add(obj.ModelType); foreach (var member in obj) @@ -47,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures hashCodeCombiner.Add(member); } - return hashCodeCombiner.CombinedHash; + return hashCodeCombiner.ToHashCode(); } } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/TagBuilder.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/TagBuilder.cs index e3e4edfbb8..4b6d8b6fcb 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/TagBuilder.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/TagBuilder.cs @@ -37,6 +37,34 @@ namespace Microsoft.AspNetCore.Mvc.Rendering TagName = tagName; } + /// + /// Creates a copy of the HTML tag passed as . + /// + /// Tag to copy. + public TagBuilder(TagBuilder tagBuilder) + { + if (tagBuilder == null) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(tagBuilder)); + } + + if (tagBuilder._attributes != null) + { + foreach (var tag in tagBuilder._attributes) + { + Attributes.Add(tag); + } + } + + if (tagBuilder._innerHtml != null) + { + tagBuilder.InnerHtml.CopyTo(InnerHtml); + } + + TagName = tagBuilder.TagName; + TagRenderMode = tagBuilder.TagRenderMode; + } + /// /// Gets the set of attributes that will be written to the tag. /// diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/TagBuilderTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/TagBuilderTest.cs index 0132887614..3ceaeef37b 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/TagBuilderTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/TagBuilderTest.cs @@ -253,5 +253,64 @@ namespace Microsoft.AspNetCore.Mvc.Core.Rendering // Assert Assert.Equal("Hello", HtmlContentUtilities.HtmlContentToString(tag)); } + + [Fact] + public void Constructor_Copy_CopiesTagRenderMode() + { + // Arrange + var originalTagBuilder = new TagBuilder("p"); + originalTagBuilder.TagRenderMode = TagRenderMode.SelfClosing; + + // Act + var clonedTagBuilder = new TagBuilder(originalTagBuilder); + + + // Assert + Assert.Equal(originalTagBuilder.TagRenderMode, clonedTagBuilder.TagRenderMode); + } + + [Fact] + public void Constructor_Copy_DoesShallowCopyOfInnerHtml() + { + // Arrange + var originalTagBuilder = new TagBuilder("p"); + originalTagBuilder.InnerHtml.AppendHtml("Hello"); + + // Act + var clonedTagBuilder = new TagBuilder(originalTagBuilder); + + // Assert + Assert.NotEqual(originalTagBuilder.InnerHtml, clonedTagBuilder.InnerHtml); + Assert.Equal(HtmlContentUtilities.HtmlContentToString(originalTagBuilder.RenderBody()), HtmlContentUtilities.HtmlContentToString(clonedTagBuilder.RenderBody())); + } + + [Fact] + public void Constructor_Copy_DoesShallowCopyOfAttributes() + { + // Arrange + var originalTagBuilder = new TagBuilder("p"); + originalTagBuilder.AddCssClass("class1"); + + // Act + var clonedTagBuilder = new TagBuilder(originalTagBuilder); + + // Assert + Assert.NotSame(originalTagBuilder.Attributes, clonedTagBuilder.Attributes); + Assert.Equal(originalTagBuilder.Attributes, clonedTagBuilder.Attributes); + } + + [Fact] + public void Constructor_Copy_CopiesTagName() + { + // Arrange + var originalTagBuilder = new TagBuilder("p"); + + // Act + + var clonedTagBuilder = new TagBuilder(originalTagBuilder); + + // Assert + Assert.Equal(originalTagBuilder.TagName, clonedTagBuilder.TagName); + } } } \ No newline at end of file diff --git a/src/Mvc/test/Mvc.FunctionalTests/ApiBehaviorTest.cs b/src/Mvc/test/Mvc.FunctionalTests/ApiBehaviorTest.cs index ca92aba1b6..6762102451 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/contact") { - Content = new StringContent("some content", Encoding.UTF7, "application/json"), + Content = new StringContent("some content", Encoding.Latin1, "application/json"), }; // Act diff --git a/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs b/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs index 40ea303614..c5c4ae51fd 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs @@ -154,17 +154,15 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(expected, await response.Content.ReadAsStringAsync()); } - [Theory] - [InlineData(65 * 1024)] - [InlineData(2 * 1024 * 1024)] - public virtual async Task Formatting_LargeObject(int size) + [Fact] + public virtual async Task Formatting_LargeObject() { // Arrange - var expectedName = "This is long so we can test large objects " + new string('a', size); + var expectedName = "This is long so we can test large objects " + new string('a', 1024 * 65); var expected = $"{{\"id\":10,\"name\":\"{expectedName}\",\"streetName\":null}}"; // Act - var response = await Client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.LargeObjectResult)}/{size}"); + var response = await Client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.LargeObjectResult)}"); // Assert await response.AssertStatusCodeAsync(HttpStatusCode.OK); diff --git a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs index b69ed7dc40..67ae87d482 100644 --- a/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs +++ b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonOutputFormatterController.cs @@ -44,12 +44,12 @@ namespace FormatterWebSite.Controllers ["Key3"] = null, }; - [HttpGet("{size:int}")] - public ActionResult LargeObjectResult(int size) => + [HttpGet] + public ActionResult LargeObjectResult() => new SimpleModel { Id = 10, - Name = "This is long so we can test large objects " + new string('a', size), + Name = "This is long so we can test large objects " + new string('a', 1024 * 65), }; [HttpGet] diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs b/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs index 5250fed054..57f163b513 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.E2ETesting; -using Microsoft.AspNetCore.Testing; using Templates.Test.Helpers; using Xunit; @@ -11,4 +10,3 @@ using Xunit; [assembly: Microsoft.AspNetCore.E2ETesting.AssemblyFixture(typeof(ProjectFactoryFixture))] [assembly: Microsoft.AspNetCore.E2ETesting.AssemblyFixture(typeof(SeleniumStandaloneServer))] -[assembly: QuarantinedTest("Investigation pending in https://github.com/dotnet/aspnetcore/issues/20479")] diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplates.Tests.csproj b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplates.Tests.csproj index d9d6b2dda8..77e0ab1128 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplates.Tests.csproj +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplates.Tests.csproj @@ -9,6 +9,7 @@ $(DefineConstants);XPLAT true + true false true diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs index ffa5da4de1..decd986856 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs @@ -330,10 +330,11 @@ namespace Templates.Test } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23639")] public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works() { var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual", Output); - project.TargetFramework = "netstandard2.1"; + project.RuntimeIdentifier = "browser-wasm"; var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "-au", diff --git a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs index 41279ca0aa..d268ce8742 100644 --- a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs @@ -44,7 +44,7 @@ namespace Templates.Test.Helpers DiagnosticsMessageSink = DiagnosticsMessageSink, ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty) }; - project.ProjectName = $"AspNet.{key}.{project.ProjectGuid}"; + project.ProjectName = $"AspNet.{project.ProjectGuid}"; var assemblyPath = GetType().Assembly; var basePath = GetTemplateFolderBasePath(assemblyPath); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/Worker-FSharp.fsproj.in b/src/ProjectTemplates/Web.ProjectTemplates/Worker-FSharp.fsproj.in new file mode 100644 index 0000000000..da9f1118cf --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/Worker-FSharp.fsproj.in @@ -0,0 +1,19 @@ + + + + ${DefaultNetCoreTargetFramework} + dotnet-Company.Application1-53bc9b9d-9d6a-45d4-8429-2a2761773502 + True + Company.Application1 + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/cs-CZ/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/cs-CZ/strings.json new file mode 100644 index 0000000000..848fb37c87 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/cs-CZ/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Služba pracovního procesu", + "description": "Šablona prázdného projektu pro vytvoření služby pracovního procesu" + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/de-DE/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/de-DE/strings.json new file mode 100644 index 0000000000..ecec877156 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/de-DE/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Workerdienst", + "description": "Eine leere Projektvorlage zum Erstellen eines Workerdiensts." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/dotnetcli.host.json new file mode 100644 index 0000000000..b1cf98e39b --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "Framework": { + "longName": "framework" + }, + "skipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "ExcludeLaunchSettings": { + "longName": "exclude-launch-settings", + "shortName": "" + } + }, + "usageExamples": [ + "" + ] +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/en/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/en/strings.json new file mode 100644 index 0000000000..0b13cebb97 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/en/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Worker Service", + "description": "An empty project template for creating a worker service." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/es-ES/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/es-ES/strings.json new file mode 100644 index 0000000000..4e3cf14930 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/es-ES/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Servicio de trabajo", + "description": "Una plantilla de proyecto vacío para crear un servicio de trabajo." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/fr-FR/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/fr-FR/strings.json new file mode 100644 index 0000000000..fd4416d5fe --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/fr-FR/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Service Worker", + "description": "Modèle de projet vide pour la création d'un service Worker." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/it-IT/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/it-IT/strings.json new file mode 100644 index 0000000000..7bdfc52e86 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/it-IT/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Servizio di ruolo di lavoro", + "description": "Modello di progetto vuoto per la creazione di servizio del ruolo di lavoro." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ja-JP/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ja-JP/strings.json new file mode 100644 index 0000000000..2de2e6b432 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ja-JP/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "ワーカー サービス", + "description": "ワーカー サービスを作成するための空のプロジェクト テンプレート。" + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ko-KR/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ko-KR/strings.json new file mode 100644 index 0000000000..4c48bf1283 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ko-KR/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "작업자 서비스", + "description": "작업자 서비스를 만들기 위한 빈 프로젝트의 템플릿입니다." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pl-PL/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pl-PL/strings.json new file mode 100644 index 0000000000..e4b11c845e --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pl-PL/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Usługa procesu roboczego", + "description": "Szablon pustego projektu służący do tworzenia usługi procesu roboczego." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pt-BR/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pt-BR/strings.json new file mode 100644 index 0000000000..d61f922597 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/pt-BR/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Serviço de Trabalho", + "description": "Um modelo de projeto vazio para criar um serviço de trabalho." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ru-RU/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ru-RU/strings.json new file mode 100644 index 0000000000..457c379060 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/ru-RU/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Служба рабочих ролей", + "description": "Шаблон пустого проекта для создания службы рабочих ролей." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/template.json new file mode 100644 index 0000000000..d3b56d1431 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/template.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ + "Common", + "Worker", + "Web" + ], + "name": "Worker Service", + "generatorVersions": "[1.0.0.0-*)", + "description": "An empty project template for creating a worker service.", + "groupIdentity": "Microsoft.Worker.Empty", + "precedence": "7000", + "identity": "Microsoft.Worker.Empty.FSharp.5.0", + "shortName": "worker", + "tags": { + "language": "F#", + "type": "project" + }, + "sourceName": "Company.Application1", + "preferNameDirectory": true, + "guids": [ + "53bc9b9d-9d6a-45d4-8429-2a2761773502" + ], + "sources": [ + { + "modifiers": [ + { + "condition": "(ExcludeLaunchSettings)", + "exclude": [ + "Properties/launchSettings.json" + ] + } + ] + } + ], + "symbols": { + "ExcludeLaunchSettings": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to exclude launchSettings.json from the generated template." + }, + "Framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net5.0", + "description": "Target net5.0" + } + ], + "replaces": "net5.0", + "defaultValue": "net5.0" + }, + "copyrightYear": { + "type": "generated", + "generator": "now", + "replaces": "copyrightYear", + "parameters": { + "format": "yyyy" + } + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "If specified, skips the automatic restore of the project on create.", + "defaultValue": "false" + } + }, + "primaryOutputs": [ + { + "path": "Company.Application1.fsproj" + } + ], + "defaultName": "WorkerService", + "postActions": [ + { + "condition": "(!skipRestore)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } + ] +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/tr-TR/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/tr-TR/strings.json new file mode 100644 index 0000000000..9a2afe2425 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/tr-TR/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "Çalışan Hizmeti", + "description": "Çalışan hizmeti oluşturmaya yönelik bir boş proje şablonu." + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3.host.json new file mode 100644 index 0000000000..c64e1a0951 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3.host.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json.schemastore.org/vs-2017.3.host", + "name": { + "text": "Worker Service", + "package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}", + "id": "1025" + }, + "description": { + "text": "An empty project template for creating a worker service.", + "package": "{0CD94836-1526-4E85-87D3-FB5274C5AFC9}", + "id": "1026" + }, + "order": 300, + "icon": "vs-2017.3/Worker.png", + "learnMoreLink": "https://go.microsoft.com/fwlink/?linkid=2072778", + "uiFilters": [ "oneaspnet" ], + "supportsDocker": true, + "excludeLaunchSettings": false, + "minFullFrameworkVersion": "4.6.1" +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3/Worker.png b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3/Worker.png new file mode 100644 index 0000000000..c9249297de Binary files /dev/null and b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/vs-2017.3/Worker.png differ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-CN/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-CN/strings.json new file mode 100644 index 0000000000..2767f38f9a --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-CN/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "辅助角色服务", + "description": "用于创建辅助角色服务的空项目模板。" + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-TW/strings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-TW/strings.json new file mode 100644 index 0000000000..53135ee645 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/.template.config/zh-TW/strings.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "strings": { + "name": "背景工作服務", + "description": "用於建立背景工作服務的空白專案範本。" + } +} \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Program.fs b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Program.fs new file mode 100644 index 0000000000..920df56e6b --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Program.fs @@ -0,0 +1,20 @@ +namespace Company.Application1 + +open System +open System.Collections.Generic +open System.Linq +open System.Threading.Tasks +open Microsoft.Extensions.DependencyInjection +open Microsoft.Extensions.Hosting + +module Program = + let createHostBuilder args = + Host.CreateDefaultBuilder(args) + .ConfigureServices(fun hostContext services -> + services.AddHostedService() |> ignore) + + [] + let main args = + createHostBuilder(args).Build().Run() + + 0 // exit code \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Properties/launchSettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Properties/launchSettings.json new file mode 100644 index 0000000000..2c42dbde5c --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Company.Application1": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Worker.fs b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Worker.fs new file mode 100644 index 0000000000..56d6c27ff7 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/Worker.fs @@ -0,0 +1,21 @@ +namespace Company.Application1 + +open System +open System.Collections.Generic +open System.Linq +open System.Threading +open System.Threading.Tasks +open Microsoft.Extensions.Hosting +open Microsoft.Extensions.Logging + +type Worker(logger: ILogger) = + inherit BackgroundService() + + override _.ExecuteAsync(ct: CancellationToken) = + async { + while not ct.IsCancellationRequested do + logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now) + do! Async.Sleep(1000) + } + |> Async.StartAsTask + :> Task // need to convert into the parameter-less task \ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.Development.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.Development.json new file mode 100644 index 0000000000..8983e0fc1c --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.json new file mode 100644 index 0000000000..8983e0fc1c --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-FSharp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Microsoft.AspNetCore.Razor.Tools.csproj b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Microsoft.AspNetCore.Razor.Tools.csproj index 30885c51f1..1489542b02 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Microsoft.AspNetCore.Razor.Tools.csproj +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/Microsoft.AspNetCore.Razor.Tools.csproj @@ -20,6 +20,7 @@ false + false diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs index 2814308b10..26f4cb8fec 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests @@ -10,6 +11,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests public class WasmCompressionTests { [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23397")] public async Task Publish_UpdatesFilesWhenSourcesChange() { // Arrange diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs index adbe0c18f4..f723ccce50 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs @@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests } [Fact] - [QuarantinedTest] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23397")] public async Task Publish_HostedApp_WithSatelliteAssemblies() { // Arrange @@ -550,7 +550,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests // Regression test to verify satellite assemblies from the blazor app are copied to the published app's wwwroot output directory as // part of publishing in VS [Fact] - [QuarantinedTest] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23397")] public async Task Publish_HostedApp_VisualStudio_WithSatelliteAssemblies() { // Simulates publishing the same way VS does by setting BuildProjectReferences=false. diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj b/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj index 42cb964335..552d192460 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/Microsoft.NET.Sdk.Razor.csproj @@ -30,7 +30,7 @@ ReferenceOutputAssembly="false" IsImplicityDefined="false" SkipGetTargetFrameworkProperties="true" - UndefineProperties="TargetFramework;TargetFrameworks" /> + UndefineProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier;PublishDir" /> diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs index febefde6c8..4a9a1de687 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp.cs @@ -133,9 +133,9 @@ namespace Microsoft.AspNetCore.Authorization { public DefaultAuthorizationService(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider, Microsoft.AspNetCore.Authorization.IAuthorizationHandlerProvider handlers, Microsoft.Extensions.Logging.ILogger logger, Microsoft.AspNetCore.Authorization.IAuthorizationHandlerContextFactory contextFactory, Microsoft.AspNetCore.Authorization.IAuthorizationEvaluator evaluator, Microsoft.Extensions.Options.IOptions options) { } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, System.Collections.Generic.IEnumerable requirements) { throw null; } + public virtual System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, System.Collections.Generic.IEnumerable requirements) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, string policyName) { throw null; } + public virtual System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, string policyName) { throw null; } } public partial interface IAuthorizationEvaluator { diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs index febefde6c8..4a9a1de687 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs @@ -133,9 +133,9 @@ namespace Microsoft.AspNetCore.Authorization { public DefaultAuthorizationService(Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider, Microsoft.AspNetCore.Authorization.IAuthorizationHandlerProvider handlers, Microsoft.Extensions.Logging.ILogger logger, Microsoft.AspNetCore.Authorization.IAuthorizationHandlerContextFactory contextFactory, Microsoft.AspNetCore.Authorization.IAuthorizationEvaluator evaluator, Microsoft.Extensions.Options.IOptions options) { } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, System.Collections.Generic.IEnumerable requirements) { throw null; } + public virtual System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, System.Collections.Generic.IEnumerable requirements) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, string policyName) { throw null; } + public virtual System.Threading.Tasks.Task AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object? resource, string policyName) { throw null; } } public partial interface IAuthorizationEvaluator { diff --git a/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs b/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs index e8f63a2d06..1dbcb3ed37 100644 --- a/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs +++ b/src/Security/Authorization/Core/src/DefaultAuthorizationService.cs @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Authorization /// A flag indicating whether authorization has succeeded. /// This value is true when the user fulfills the policy otherwise false. /// - public async Task AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable requirements) + public virtual async Task AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable requirements) { if (requirements == null) { @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Authorization /// A flag indicating whether authorization has succeeded. /// This value is true when the user fulfills the policy otherwise false. /// - public async Task AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName) + public virtual async Task AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName) { if (policyName == null) { diff --git a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs index 119cc75c92..d876781f96 100644 --- a/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs @@ -11,6 +11,9 @@ namespace Microsoft.AspNetCore.Authorization { public class AuthorizationMiddleware { + // AppContext switch used to control whether HttpContext or endpoint is passed as a resource to AuthZ + private const string SuppressUseHttpContextAsAuthorizationResource = "Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource"; + // Property key is used by Endpoint routing to determine if Authorization has run private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked"; private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object(); @@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.Authorization private readonly RequestDelegate _next; private readonly IAuthorizationPolicyProvider _policyProvider; - public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) + public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) { _next = next ?? throw new ArgumentNullException(nameof(next)); _policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider)); @@ -61,8 +64,17 @@ namespace Microsoft.AspNetCore.Authorization return; } - var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: context); - + object? resource; + if (AppContext.TryGetSwitch(SuppressUseHttpContextAsAuthorizationResource, out var useEndpointAsResource) && useEndpointAsResource) + { + resource = endpoint; + } + else + { + resource = context; + } + + var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource); var authorizationMiddlewareResultHandler = context.RequestServices.GetRequiredService(); await authorizationMiddlewareResultHandler.HandleAsync(_next, context, policy, authorizeResult); } diff --git a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs index effe426a3b..89de50e3d4 100644 --- a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs +++ b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Authorization.Test.TestObjects; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -314,7 +315,7 @@ namespace Microsoft.AspNetCore.Authorization.Test } [Fact] - public async Task AuthZResourceShouldBeHttpContextAndHaveHEndpoint() + public async Task AuthZResourceCanBeHttpContextAndHaveEndpoint() { // Arrange HttpContext resource = null; @@ -340,6 +341,33 @@ namespace Microsoft.AspNetCore.Authorization.Test Assert.Equal(endpoint, resource.GetEndpoint()); } + [Fact] + public async Task AuthZResourceShouldBeEndpointByDefaultWithCompatSwitch() + { + AppContext.SetSwitch("Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource", isEnabled: true); + + // Arrange + object resource = null; + var policy = new AuthorizationPolicyBuilder().RequireAssertion(c => + { + resource = c.Resource; + return true; + }).Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var endpoint = CreateEndpoint(new AuthorizeAttribute()); + var context = GetHttpContext(endpoint: endpoint); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.Equal(endpoint, resource); + } + [Fact] public async Task Invoke_RequireUnknownRoleShouldForbid() { @@ -410,7 +438,6 @@ namespace Microsoft.AspNetCore.Authorization.Test private AuthorizationMiddleware CreateMiddleware(RequestDelegate requestDelegate = null, IAuthorizationPolicyProvider policyProvider = null) { requestDelegate = requestDelegate ?? ((context) => Task.CompletedTask); - return new AuthorizationMiddleware(requestDelegate, policyProvider); } diff --git a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs index ce3d115e2a..9ad2c62e8a 100644 --- a/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs +++ b/src/Servers/Connections.Abstractions/src/DefaultConnectionContext.cs @@ -26,7 +26,6 @@ namespace Microsoft.AspNetCore.Connections public DefaultConnectionContext() : this(Guid.NewGuid().ToString()) { - ConnectionClosed = _connectionClosedTokenSource.Token; } /// @@ -45,6 +44,8 @@ namespace Microsoft.AspNetCore.Connections Features.Set(this); Features.Set(this); Features.Set(this); + + ConnectionClosed = _connectionClosedTokenSource.Token; } public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application) diff --git a/src/Servers/HttpSys/src/FeatureContext.cs b/src/Servers/HttpSys/src/FeatureContext.cs index ab569e1bfa..4845013a6b 100644 --- a/src/Servers/HttpSys/src/FeatureContext.cs +++ b/src/Servers/HttpSys/src/FeatureContext.cs @@ -635,6 +635,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys private static TimeSpan? GetCacheTtl(RequestContext requestContext) { var response = requestContext.Response; + // A 304 response is supposed to have the same headers as its associated 200 response, including Cache-Control, but the 304 response itself + // should not be cached. Otherwise Http.Sys will serve the 304 response to all requests without checking conditional headers like If-None-Match. + if (response.StatusCode == StatusCodes.Status304NotModified) + { + return null; + } + // Only consider kernel-mode caching if the Cache-Control response header is present. var cacheControlHeader = response.Headers[HeaderNames.CacheControl]; if (string.IsNullOrEmpty(cacheControlHeader)) diff --git a/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs index 57e5722557..38c76dc1ab 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Http2Tests.cs @@ -20,6 +20,84 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests { public class Http2Tests { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + private static readonly Version Win10_Regressed_DataFrame = new Version(10, 0, 20145, 0); + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + public async Task EmptyResponse_200() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + // Default 200 + return Task.CompletedTask; + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + + [ConditionalFact] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] + public async Task ResponseWithData_Success() + { + using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => + { + return httpContext.Response.WriteAsync("Hello World"); + }); + + await new HostBuilder() + .UseHttp2Cat(address, async h2Connection => + { + await h2Connection.InitializeConnectionAsync(); + + h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1."); + + await h2Connection.StartStreamAsync(1, Http2Utilities.BrowserRequestHeaders, endStream: true); + + await h2Connection.ReceiveHeadersAsync(1, decodedHeaders => + { + Assert.Equal("200", decodedHeaders[HeaderNames.Status]); + }); + + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); + + h2Connection.Logger.LogInformation("Connection stopped."); + }) + .Build().RunAsync(); + } + [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/17420")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_19H1, SkipReason = "This is last version without GoAway support")] @@ -95,6 +173,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. @@ -135,6 +221,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); // Http.Sys doesn't send a final GoAway unless we ignore the first one and send 200 additional streams. @@ -152,6 +246,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); } @@ -171,6 +273,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, streamId, endOfStream: true, length: 0); h2Connection.Logger.LogInformation("Connection stopped."); @@ -180,7 +290,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests [ConditionalFact] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")] - public async Task AppException_BeforeHeaders_500() + public async Task AppException_BeforeResponseHeaders_500() { using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext => { @@ -202,6 +312,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); h2Connection.Logger.LogInformation("Connection stopped."); @@ -266,8 +384,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests Assert.Equal("200", decodedHeaders[HeaderNames.Status]); }); - var resetFrame = await h2Connection.ReceiveFrameAsync(); - Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR); + var frame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(frame, 1, endOfStream: false, length: 0); + + frame = await h2Connection.ReceiveFrameAsync(); + } + Http2Utilities.VerifyResetFrame(frame, expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR); h2Connection.Logger.LogInformation("Connection stopped."); }) @@ -395,6 +521,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests Assert.Equal("200", decodedHeaders[HeaderNames.Status]); }); + var dataFrame = await h2Connection.ReceiveFrameAsync(); + Http2Utilities.VerifyDataFrame(dataFrame, expectedStreamId: 1, endOfStream: false, length: 0); + var resetFrame = await h2Connection.ReceiveFrameAsync(); Http2Utilities.VerifyResetFrame(resetFrame, expectedStreamId: 1, expectedErrorCode: (Http2ErrorCode)1111); @@ -648,6 +777,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests }); var dataFrame = await h2Connection.ReceiveFrameAsync(); + if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame) + { + // TODO: Remove when the regression is fixed. + // https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163 + Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0); + + dataFrame = await h2Connection.ReceiveFrameAsync(); + } Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0); var resetFrame = await h2Connection.ReceiveFrameAsync(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs index 1112420ed6..ed9fffebbb 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs @@ -80,11 +80,33 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await SendRequestAsync(address)); Assert.Equal("2", await SendRequestAsync(address)); } } + [ConditionalFact] + public async Task Caching_304_NotCached() + { + var requestCount = 1; + using (Utilities.CreateHttpServer(out string address, httpContext => + { + // 304 responses are not themselves cachable. Their cache header mirrors the resource's original cache header. + httpContext.Response.StatusCode = StatusCodes.Status304NotModified; + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. + Assert.Equal("1", await SendRequestAsync(address, StatusCodes.Status304NotModified)); + Assert.Equal("2", await SendRequestAsync(address, StatusCodes.Status304NotModified)); + } + } + [ConditionalFact] [QuarantinedTest("https://github.com/dotnet/aspnetcore-internal/issues/2207")] public async Task Caching_WithoutContentType_Cached_OnWin7AndWin2008R2() @@ -105,6 +127,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await SendRequestAsync(address)); Assert.Equal("1", await SendRequestAsync(address)); } @@ -144,6 +167,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await SendRequestAsync(address)); Assert.Equal("1", await SendRequestAsync(address)); } @@ -314,6 +338,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await SendRequestAsync(address)); Assert.Equal("2", await SendRequestAsync(address)); } @@ -335,6 +360,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests Assert.Null(httpContext.Response.ContentLength); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await SendRequestAsync(address)); Assert.Equal("1", await SendRequestAsync(address)); } @@ -353,6 +379,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await GetFileAsync(address)); Assert.Equal("2", await GetFileAsync(address)); } @@ -372,6 +399,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); })) { + address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. Assert.Equal("1", await GetFileAsync(address)); Assert.Equal("1", await GetFileAsync(address)); } @@ -400,13 +428,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests switch (status) { case 206: // 206 (Partial Content) is not cached + case 304: // 304 (Not Modified) is not cached case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw continue; } requestCount = 1; + var query = "?" + Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests. try { - Assert.Equal("1", await SendRequestAsync(address + status, status)); + Assert.Equal("1", await SendRequestAsync(address + status + query, status)); } catch (Exception ex) { @@ -414,7 +444,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } try { - Assert.Equal("1", await SendRequestAsync(address + status, status)); + Assert.Equal("1", await SendRequestAsync(address + status + query, status)); } catch (Exception ex) { diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs index d61c6fa5da..f883d88f83 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/ClientCertificateTests.cs @@ -34,7 +34,6 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests .WithAllHostingModels(); [ConditionalTheory] - [QuarantinedTest] [MemberData(nameof(TestVariants))] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8)] public Task HttpsNoClientCert_NoClientCert(TestVariant variant) diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 96f6e49969..46f2e263a3 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -268,7 +268,7 @@ Overriding endpoints defined in UseKestrel() because {settingName} is set to true. Binding to address(es) '{addresses}' instead. - Unrecognized scheme in server address '{address}'. Only 'http://' is supported. + Unrecognized scheme in server address '{address}'. Only 'http://' and 'https://' are supported. Headers are read-only, response has already started. diff --git a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs index 40ac8cd9ff..31e4270131 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private IDictionary ReadCertificates() { - var certificates = new Dictionary(0); + var certificates = new Dictionary(0, StringComparer.OrdinalIgnoreCase); var certificatesConfig = _configuration.GetSection(CertificatesKey).GetChildren(); foreach (var certificateConfig in certificatesConfig) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs index 33c7b920f3..c914595bc9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/HPackHeaderWriter.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 case 404: case 500: // Status codes which exist in the HTTP/2 StaticTable. - return HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], buffer, out length); + return HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.GetStatusIndex(statusCode), buffer, out length); default: const string name = ":status"; var value = StatusCodes.ToStatusString(statusCode); diff --git a/src/Servers/Kestrel/Kestrel/test/ConfigurationReaderTests.cs b/src/Servers/Kestrel/Kestrel/test/ConfigurationReaderTests.cs index a83e90818d..06345cc462 100644 --- a/src/Servers/Kestrel/Kestrel/test/ConfigurationReaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/ConfigurationReaderTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Authentication; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.Configuration; using Xunit; @@ -68,6 +69,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests Assert.True(storeCert.AllowInvalid); } + [Fact] + public void ReadCertificatesSection_IsCaseInsensitive() + { + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Certificates:filecert:Path", "/path/cert.pfx"), + new KeyValuePair("CERTIFICATES:FILECERT:PASSWORD", "certpassword"), + }).Build(); + var reader = new ConfigurationReader(config); + var certificates = reader.Certificates; + Assert.NotNull(certificates); + Assert.Equal(1, certificates.Count); + + var fileCert = certificates["FiLeCeRt"]; + Assert.True(fileCert.IsFileCert); + Assert.False(fileCert.IsStoreCert); + Assert.Equal("/path/cert.pfx", fileCert.Path); + Assert.Equal("certpassword", fileCert.Password); + } + + [Fact] + public void ReadCertificatesSection_ThrowsOnCaseInsensitiveDuplicate() + { + var exception = Assert.Throws(() => + new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Certificates:filecert:Password", "certpassword"), + new KeyValuePair("Certificates:FILECERT:Password", "certpassword"), + }).Build()); + + Assert.Contains(CoreStrings.KeyAlreadyExists, exception.Message); + } + [Fact] public void ReadEndpointsWhenNoEndpointsSection_ReturnsEmptyCollection() { diff --git a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs index fd5c31d032..6f2b8c9687 100644 --- a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs @@ -63,17 +63,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests [Fact] public void LibuvTransportCanBeManuallySelectedIndependentOfOrder() { +#pragma warning disable CS0618 var hostBuilder = new WebHostBuilder() .UseKestrel() .UseLibuv() .Configure(app => { }); +#pragma warning restore CS0618 Assert.IsType(hostBuilder.Build().Services.GetService()); +#pragma warning disable CS0618 var hostBuilderReversed = new WebHostBuilder() .UseLibuv() .UseKestrel() .Configure(app => { }); +#pragma warning restore CS0618 Assert.IsType(hostBuilderReversed.Build().Services.GetService()); } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs index d1b2e9a8e7..f023d5421a 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnectionListener.cs @@ -40,7 +40,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal public IHostApplicationLifetime AppLifetime => TransportContext.AppLifetime; public ILibuvTrace Log => TransportContext.Log; + +#pragma warning disable CS0618 public LibuvTransportOptions TransportOptions => TransportContext.Options; +#pragma warning restore CS0618 public EndPoint EndPoint { get; set; } @@ -131,7 +134,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { // TODO: Move thread management to LibuvTransportFactory // TODO: Split endpoint management from thread management +#pragma warning disable CS0618 for (var index = 0; index < TransportOptions.ThreadCount; index++) +#pragma warning restore CS0618 { Threads.Add(new LibuvThread(Libuv, TransportContext)); } @@ -143,7 +148,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal try { +#pragma warning disable CS0618 if (TransportOptions.ThreadCount == 1) +#pragma warning restore CS0618 { var listener = new Listener(TransportContext); _listeners.Add(listener); diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs index 973a0fec06..4323463ef2 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportContext.cs @@ -7,7 +7,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { internal class LibuvTransportContext { +#pragma warning disable CS0618 public LibuvTransportOptions Options { get; set; } +#pragma warning restore CS0618 public IHostApplicationLifetime AppLifetime { get; set; } diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs index 689c473195..2b168c0950 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransportFactory.cs @@ -17,7 +17,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal private readonly LibuvTransportContext _baseTransportContext; public LibuvTransportFactory( +#pragma warning disable CS0618 IOptions options, +#pragma warning restore CS0618 IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory) { @@ -37,7 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"); var trace = new LibuvTrace(logger); +#pragma warning disable CS0618 var threadCount = options.Value.ThreadCount; +#pragma warning restore CS0618 if (threadCount <= 0) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs index 6279764101..92c89456b1 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/Listener.cs @@ -37,7 +37,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal return Thread.PostAsync(listener => { listener.ListenSocket = listener.CreateListenSocket(); +#pragma warning disable CS0618 listener.ListenSocket.Listen(TransportContext.Options.Backlog, ConnectionCallback, listener); +#pragma warning restore CS0618 }, this); } @@ -66,7 +68,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); +#pragma warning disable CS0618 socket.NoDelay(TransportContext.Options.NoDelay); +#pragma warning restore CS0618 if (!useFileHandle) { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs index 528ec5ca3b..831da6ae8b 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs @@ -109,7 +109,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal } var options = TransportContext.Options; +#pragma warning disable CS0618 var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions, options.MaxReadBufferSize, options.MaxWriteBufferSize); +#pragma warning restore CS0618 connection.Start(); bool accepted = _acceptQueue.Writer.TryWrite(connection); @@ -128,7 +130,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal try { socket.Init(Thread.Loop, Thread.QueueCloseHandle); +#pragma warning disable CS0618 socket.NoDelay(TransportContext.Options.NoDelay); +#pragma warning restore CS0618 } catch { diff --git a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs index 070ee5a73b..6db3e4bffc 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs @@ -69,8 +69,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal ListenPipe = new UvPipeHandle(Log); ListenPipe.Init(Thread.Loop, Thread.QueueCloseHandle, false); ListenPipe.Bind(_pipeName); +#pragma warning disable CS0618 ListenPipe.Listen(TransportContext.Options.Backlog, (pipe, status, error, state) => ((ListenerPrimary)state).OnListenPipe(pipe, status, error), this); +#pragma warning restore CS0618 } private void OnListenPipe(UvStreamHandle pipe, int status, UvException error) diff --git a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs index 0aa477f3af..ee96297f1d 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/LibuvTransportOptions.cs @@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// /// Provides programmatic configuration of Libuv transport features. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public class LibuvTransportOptions { /// @@ -17,6 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// /// Defaults to half of rounded down and clamped between 1 and 16. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public int ThreadCount { get; set; } = ProcessorThreadCount; /// @@ -25,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// /// Defaults to true. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public bool NoDelay { get; set; } = true; /// @@ -33,10 +36,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// /// Defaults to 128. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public int Backlog { get; set; } = 128; + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public long? MaxReadBufferSize { get; set; } = 1024 * 1024; + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public long? MaxWriteBufferSize { get; set; } = 64 * 1024; internal Func> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; diff --git a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs index 8f0986d334..fc795fcde4 100644 --- a/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs +++ b/src/Servers/Kestrel/Transport.Libuv/src/WebHostBuilderLibuvExtensions.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting { + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public static class WebHostBuilderLibuvExtensions { /// @@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Hosting /// /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public static IWebHostBuilder UseLibuv(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => @@ -40,6 +42,7 @@ namespace Microsoft.AspNetCore.Hosting /// /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder. /// + [Obsolete("The libuv transport is obsolete and will be removed in a future release. See https://aka.ms/libuvtransport for details.", error: false)] public static IWebHostBuilder UseLibuv(this IWebHostBuilder hostBuilder, Action configureOptions) { return hostBuilder.UseLibuv().ConfigureServices(services => diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportFactoryTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportFactoryTests.cs index 60d8f55dc6..b1230babdb 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportFactoryTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportFactoryTests.cs @@ -18,7 +18,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests [InlineData(-1337)] public void StartWithNonPositiveThreadCountThrows(int threadCount) { +#pragma warning disable CS0618 var options = new LibuvTransportOptions { ThreadCount = threadCount }; +#pragma warning restore CS0618 var exception = Assert.Throws(() => new LibuvTransportFactory(Options.Create(options), new LifetimeNotImplemented(), Mock.Of())); @@ -30,7 +32,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests public void LoggerCategoryNameIsLibuvTransportNamespace() { var mockLoggerFactory = new Mock(); +#pragma warning disable CS0618 new LibuvTransportFactory(Options.Create(new LibuvTransportOptions()), new LifetimeNotImplemented(), mockLoggerFactory.Object); +#pragma warning restore CS0618 mockLoggerFactory.Verify(factory => factory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv")); } } diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportOptionsTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportOptionsTests.cs index 8651bdb2ff..aa6b705047 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportOptionsTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportOptionsTests.cs @@ -14,9 +14,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests // Ideally we'd mock Environment.ProcessorCount to test edge cases. var expected = Clamp(Environment.ProcessorCount >> 1, 1, 16); +#pragma warning disable CS0618 var information = new LibuvTransportOptions(); Assert.Equal(expected, information.ThreadCount); +#pragma warning restore CS0618 } private static int Clamp(int value, int min, int max) diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index 12f802864d..b7d1e16fd5 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -191,7 +191,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var transportContext = new TestLibuvTransportContext { +#pragma warning disable CS0618 Options = new LibuvTransportOptions { ThreadCount = threadCount } +#pragma warning restore CS0618 }; await using var transport = new LibuvConnectionListener(transportContext, listenOptions.EndPoint); diff --git a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs b/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs index 1bccf57d32..a2d49bc8d0 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/TestHelpers/TestLibuvTransportContext.cs @@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers AppLifetime = new LifetimeNotImplemented(); Log = new LibuvTrace(logger); +#pragma warning disable CS0618 Options = new LibuvTransportOptions { ThreadCount = 1 }; +#pragma warning restore CS0618 } } } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/HPackDecoderBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/HPackDecoderBenchmark.cs new file mode 100644 index 0000000000..31382bbf5e --- /dev/null +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/HPackDecoderBenchmark.cs @@ -0,0 +1,135 @@ +// 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.Linq; +using System.Net.Http.HPack; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Performance +{ + public class HPackDecoderBenchmark + { + // Indexed Header Field Representation - Dynamic Table - Index 62 (first index in dynamic table) + private static readonly byte[] _indexedHeaderDynamic = new byte[] { 0xbe }; + + private static readonly byte[] _literalHeaderFieldWithoutIndexingNewName = new byte[] { 0x00 }; + + private const string _headerNameString = "new-header"; + + private static readonly byte[] _headerNameBytes = Encoding.ASCII.GetBytes(_headerNameString); + + private static readonly byte[] _headerName = new byte[] { (byte)_headerNameBytes.Length } + .Concat(_headerNameBytes) + .ToArray(); + + private const string _headerValueString = "value"; + + private static readonly byte[] _headerValueBytes = Encoding.ASCII.GetBytes(_headerValueString); + + private static readonly byte[] _headerValue = new byte[] { (byte)_headerValueBytes.Length } + .Concat(_headerValueBytes) + .ToArray(); + + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName = _literalHeaderFieldWithoutIndexingNewName + .Concat(_headerName) + .Concat(_headerValue) + .ToArray(); + + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Large; + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Multiple; + private static readonly byte[] _indexedHeaderDynamic_Multiple; + + static HPackDecoderBenchmark() + { + string string8193 = new string('a', 8193); + + _literalHeaderFieldNeverIndexed_NewName_Large = _literalHeaderFieldWithoutIndexingNewName + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .ToArray(); + + _literalHeaderFieldNeverIndexed_NewName_Multiple = _literalHeaderFieldNeverIndexed_NewName + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .ToArray(); + + _indexedHeaderDynamic_Multiple = _indexedHeaderDynamic + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .ToArray(); + } + + private HPackDecoder _decoder; + private TestHeadersHandler _testHeadersHandler; + private DynamicTable _dynamicTable; + + [GlobalSetup] + public void GlobalSetup() + { + _dynamicTable = new DynamicTable(maxSize: 4096); + _dynamicTable.Insert(_headerNameBytes, _headerValueBytes); + _decoder = new HPackDecoder(maxDynamicTableSize: 4096, maxHeadersLength: 65536, _dynamicTable); + _testHeadersHandler = new TestHeadersHandler(); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Large() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Large, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Multiple() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Multiple, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesIndexedHeaderField_DynamicTable() + { + _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesIndexedHeaderField_DynamicTable_Multiple() + { + _decoder.Decode(_indexedHeaderDynamic_Multiple, endHeaders: true, handler: _testHeadersHandler); + } + + private class TestHeadersHandler : IHttpHeadersHandler + { + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + } + + public void OnHeadersComplete(bool endStream) + { + } + + public void OnStaticIndexedHeader(int index) + { + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + } + } + } +} diff --git a/src/Servers/Kestrel/samples/SampleApp/Startup.cs b/src/Servers/Kestrel/samples/SampleApp/Startup.cs index 3b61d0fe91..6f6afe3fac 100644 --- a/src/Servers/Kestrel/samples/SampleApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SampleApp/Startup.cs @@ -157,11 +157,13 @@ namespace SampleApp if (string.Equals(Process.GetCurrentProcess().Id.ToString(), Environment.GetEnvironmentVariable("LISTEN_PID"))) { // Use libuv if activated by systemd, since that's currently the only transport that supports being passed a socket handle. +#pragma warning disable CS0618 hostBuilder.UseLibuv(options => { // Uncomment the following line to change the default number of libuv threads for all endpoints. // options.ThreadCount = 4; }); +#pragma warning restore CS0618 } return hostBuilder.Build().RunAsync(); diff --git a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs index 56e6f3f980..8a01fac6d9 100644 --- a/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs +++ b/src/Servers/Kestrel/samples/SystemdTestApp/Startup.cs @@ -75,11 +75,13 @@ namespace SystemdTestApp if (string.Equals(Process.GetCurrentProcess().Id.ToString(), Environment.GetEnvironmentVariable("LISTEN_PID"))) { // Use libuv if activated by systemd, since that's currently the only transport that supports being passed a socket handle. +#pragma warning disable CS0618 hostBuilder.UseLibuv(options => { // Uncomment the following line to change the default number of libuv threads for all endpoints. // options.ThreadCount = 4; }); +#pragma warning restore CS0618 } return hostBuilder.Build().RunAsync(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 43a3f9d7a2..a1cf418fe3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -300,7 +300,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - [QuarantinedTest] public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused() { TaskCompletionSource appDelegateTcs = null; @@ -365,7 +364,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - [QuarantinedTest] public async Task StreamPool_StreamIsInvalidState_DontReturnedToPool() { var serverTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -4010,7 +4008,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Theory] [InlineData(true)] [InlineData(false)] - [QuarantinedTest] public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 99bbd21ea5..c433bb90be 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -371,7 +371,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - [QuarantinedTest] public async Task DATA_Sent_TooSlowlyDueToFlowControlOnSmallWrite_AbortsConnectionAfterGracePeriod() { var mockSystemClock = _serviceContext.MockSystemClock; @@ -426,7 +425,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - [QuarantinedTest] public async Task DATA_Sent_TooSlowlyDueToOutputFlowControlOnLargeWrite_AbortsConnectionAfterRateTimeout() { var mockSystemClock = _serviceContext.MockSystemClock; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index 59af852469..9e3263bb79 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs @@ -22,14 +22,15 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 { [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] - [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81, - SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, + SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support or incompatible ciphers on Windows 8.1")] public class TlsTests : LoggedTest { private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); [ConditionalFact] [QuarantinedTest] + [SkipOnHelix("Ubuntu 20.04 disables TLS1.1 by default which SslStream requires in this scenario", Queues = "Ubuntu.2004.Amd64.Open")] public async Task TlsHandshakeRejectsTlsLessThan12() { using (var server = new TestServer(context => diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index 4849965c40..4f2f7cde91 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -219,8 +219,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] - [Repeat(20)] - [QuarantinedTest] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23405")] public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -250,7 +249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true)) { await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, - enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, + enabledSslProtocols: SslProtocols.None, checkCertificateRevocation: false); var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs index c5002a8c8c..5ab5b467e8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeaderLimitsTests.cs @@ -23,7 +23,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests [InlineData(5, 0)] [InlineData(5, 1)] [InlineData(5, 1337)] - [QuarantinedTest] public async Task ServerAcceptsRequestWithHeaderTotalSizeWithinLimit(int headerCount, int extraLimit) { var headers = MakeHeaders(headerCount); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 6257858c1c..f20418b9d1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -559,7 +559,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] - [QuarantinedTest] public async Task TraceIdentifierIsUnique() { const int identifierLength = 22; @@ -1049,6 +1048,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23628")] public async Task ContentLengthReadAsyncSingleBytesAtATime() { var testContext = new TestServiceContext(LoggerFactory); diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs index fcf32c1cd6..d55ffdce2b 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/HttpClientHttp2InteropTests.cs @@ -696,6 +696,7 @@ namespace Interop.FunctionalTests [Theory] [MemberData(nameof(SupportedSchemes))] + [QuarantinedTest] public async Task ServerReset_BeforeRequestBody_ClientBodyThrows(string scheme) { var clientEcho = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); diff --git a/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs b/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs index 4bcae2a6cf..4bd8e32597 100644 --- a/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs +++ b/src/Servers/Kestrel/test/Libuv.FunctionalTests/TransportSelector.cs @@ -12,11 +12,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null, long? maxReadBufferSize = null) { +#pragma warning disable CS0618 return new WebHostBuilder().UseLibuv(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; options.MaxReadBufferSize = maxReadBufferSize; }); +#pragma warning restore CS0618 } } } diff --git a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs index f234c2edbc..fc41de2069 100644 --- a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs +++ b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs @@ -1,8 +1,9 @@ // 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. +#nullable enable + using System; -using System.Linq; using System.Reflection; namespace Microsoft.Extensions.Internal @@ -28,7 +29,7 @@ namespace Microsoft.Extensions.Internal /// typeof(KeyValuePair{,}), and is /// typeof(KeyValuePair{string, object}). /// - public static Type ExtractGenericInterface(Type queryType, Type interfaceType) + public static Type? ExtractGenericInterface(Type queryType, Type interfaceType) { if (queryType == null) { @@ -62,9 +63,9 @@ namespace Microsoft.Extensions.Internal candidate.GetGenericTypeDefinition() == interfaceType; } - private static Type GetGenericInstantiation(Type queryType, Type interfaceType) + private static Type? GetGenericInstantiation(Type queryType, Type interfaceType) { - Type bestMatch = null; + Type? bestMatch = null; var interfaces = queryType.GetInterfaces(); foreach (var @interface in interfaces) { @@ -103,4 +104,4 @@ namespace Microsoft.Extensions.Internal } } } -} \ No newline at end of file +} diff --git a/src/Shared/E2ETesting/WaitAssert.cs b/src/Shared/E2ETesting/WaitAssert.cs index 9aa675bb9a..144715253d 100644 --- a/src/Shared/E2ETesting/WaitAssert.cs +++ b/src/Shared/E2ETesting/WaitAssert.cs @@ -23,6 +23,9 @@ namespace Microsoft.AspNetCore.E2ETesting public static void Equal(this IWebDriver driver, T expected, Func actual) => WaitAssertCore(driver, () => Assert.Equal(expected, actual())); + public static void NotEqual(this IWebDriver driver, T expected, Func actual) + => WaitAssertCore(driver, () => Assert.NotEqual(expected, actual())); + public static void True(this IWebDriver driver, Func actual) => WaitAssertCore(driver, () => Assert.True(actual())); diff --git a/src/Shared/RazorViews/BaseView.cs b/src/Shared/RazorViews/BaseView.cs index 04b112a1c1..7a1e2dc8f8 100644 --- a/src/Shared/RazorViews/BaseView.cs +++ b/src/Shared/RazorViews/BaseView.cs @@ -20,6 +20,7 @@ namespace Microsoft.Extensions.RazorViews internal abstract class BaseView { private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private static readonly char[] NewLineChars = new[] { '\r', '\n' }; private readonly Stack _textWriterStack = new Stack(); /// @@ -292,8 +293,8 @@ namespace Microsoft.Extensions.RazorViews // Split on line breaks before passing it through the encoder. return string.Join("
" + Environment.NewLine, - input.Split(new[] { "\r\n" }, StringSplitOptions.None) - .SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None)) + input.Split("\r\n", StringSplitOptions.None) + .SelectMany(s => s.Split(NewLineChars, StringSplitOptions.None)) .Select(HtmlEncoder.Encode)); } } diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs index 7ce03ec1a4..244453e0b5 100644 --- a/src/Shared/WebEncoders/WebEncoders.cs +++ b/src/Shared/WebEncoders/WebEncoders.cs @@ -26,8 +26,6 @@ namespace Microsoft.Extensions.Internal #endif static class WebEncoders { - private static readonly byte[] EmptyBytes = new byte[0]; - /// /// Decodes a base64url-encoded string. /// @@ -70,7 +68,7 @@ namespace Microsoft.Extensions.Internal // Special-case empty input if (count == 0) { - return EmptyBytes; + return Array.Empty(); } // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. @@ -117,7 +115,7 @@ namespace Microsoft.Extensions.Internal if (count == 0) { - return EmptyBytes; + return Array.Empty(); } // Assumption: input is base64url encoded without padding and contains no whitespace. diff --git a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs index 5a8fdf170f..9e93dca87c 100644 --- a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs +++ b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs @@ -25,7 +25,7 @@ namespace System.Net.Http.HPack public int MaxSize => _maxSize; - public HeaderField this[int index] + public ref readonly HeaderField this[int index] { get { @@ -42,7 +42,7 @@ namespace System.Net.Http.HPack index += _buffer.Length; } - return _buffer[index]; + return ref _buffer[index]; } } diff --git a/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs b/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs index 7f3b775582..c0f203fef4 100644 --- a/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs +++ b/src/Shared/runtime/Http2/Hpack/H2StaticTable.cs @@ -9,23 +9,22 @@ namespace System.Net.Http.HPack { internal static class H2StaticTable { - // Index of status code into s_staticDecoderTable - private static readonly Dictionary s_statusIndex = new Dictionary - { - [200] = 8, - [204] = 9, - [206] = 10, - [304] = 11, - [400] = 12, - [404] = 13, - [500] = 14, - }; - public static int Count => s_staticDecoderTable.Length; - public static HeaderField Get(int index) => s_staticDecoderTable[index]; + public static ref readonly HeaderField Get(int index) => ref s_staticDecoderTable[index]; - public static IReadOnlyDictionary StatusIndex => s_statusIndex; + public static int GetStatusIndex(int status) => + status switch + { + 200 => 8, + 204 => 9, + 206 => 10, + 304 => 11, + 400 => 12, + 404 => 13, + 500 => 14, + _ => throw new ArgumentOutOfRangeException() + }; private static readonly HeaderField[] s_staticDecoderTable = new HeaderField[] { diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs index 3fe3c86243..b07dc47d9a 100644 --- a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs +++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs @@ -5,6 +5,7 @@ #nullable enable using System.Buffers; using System.Diagnostics; +using System.Numerics; #if KESTREL using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; #endif @@ -37,7 +38,6 @@ namespace System.Net.Http.HPack // | 1 | Index (7+) | // +---+---------------------------+ private const byte IndexedHeaderFieldMask = 0x80; - private const byte IndexedHeaderFieldRepresentation = 0x80; // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1 // 0 1 2 3 4 5 6 7 @@ -45,7 +45,6 @@ namespace System.Net.Http.HPack // | 0 | 1 | Index (6+) | // +---+---+-----------------------+ private const byte LiteralHeaderFieldWithIncrementalIndexingMask = 0xc0; - private const byte LiteralHeaderFieldWithIncrementalIndexingRepresentation = 0x40; // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2 // 0 1 2 3 4 5 6 7 @@ -53,7 +52,6 @@ namespace System.Net.Http.HPack // | 0 | 0 | 0 | 0 | Index (4+) | // +---+---+-----------------------+ private const byte LiteralHeaderFieldWithoutIndexingMask = 0xf0; - private const byte LiteralHeaderFieldWithoutIndexingRepresentation = 0x00; // http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3 // 0 1 2 3 4 5 6 7 @@ -61,7 +59,6 @@ namespace System.Net.Http.HPack // | 0 | 0 | 0 | 1 | Index (4+) | // +---+---+-----------------------+ private const byte LiteralHeaderFieldNeverIndexedMask = 0xf0; - private const byte LiteralHeaderFieldNeverIndexedRepresentation = 0x10; // http://httpwg.org/specs/rfc7541.html#rfc.section.6.3 // 0 1 2 3 4 5 6 7 @@ -69,7 +66,6 @@ namespace System.Net.Http.HPack // | 0 | 0 | 1 | Max size (5+) | // +---+---------------------------+ private const byte DynamicTableSizeUpdateMask = 0xe0; - private const byte DynamicTableSizeUpdateRepresentation = 0x20; // http://httpwg.org/specs/rfc7541.html#rfc.section.5.2 // 0 1 2 3 4 5 6 7 @@ -92,6 +88,8 @@ namespace System.Net.Http.HPack private byte[] _stringOctets; private byte[] _headerNameOctets; private byte[] _headerValueOctets; + private (int start, int length)? _headerNameRange; + private (int start, int length)? _headerValueRange; private State _state = State.Ready; private byte[]? _headerName; @@ -124,107 +122,247 @@ namespace System.Net.Http.HPack { foreach (ReadOnlyMemory segment in data) { - DecodeInternal(segment.Span, endHeaders, handler); + DecodeInternal(segment.Span, handler); } CheckIncompleteHeaderBlock(endHeaders); } - public void Decode(ReadOnlySpan data, bool endHeaders, IHttpHeadersHandler? handler) + public void Decode(ReadOnlySpan data, bool endHeaders, IHttpHeadersHandler handler) { - DecodeInternal(data, endHeaders, handler); + DecodeInternal(data, handler); CheckIncompleteHeaderBlock(endHeaders); } - private void DecodeInternal(ReadOnlySpan data, bool endHeaders, IHttpHeadersHandler? handler) + private void DecodeInternal(ReadOnlySpan data, IHttpHeadersHandler handler) { - int intResult; + int currentIndex = 0; - for (int i = 0; i < data.Length; i++) + do { - byte b = data[i]; switch (_state) { case State.Ready: - // TODO: Instead of masking and comparing each prefix value, - // consider doing a 16-way switch on the first four bits (which is the max prefix size). - // Look at this once we have more concrete perf data. - if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation) + Parse(data, ref currentIndex, handler); + break; + case State.HeaderFieldIndex: + ParseHeaderFieldIndex(data, ref currentIndex, handler); + break; + case State.HeaderNameIndex: + ParseHeaderNameIndex(data, ref currentIndex, handler); + break; + case State.HeaderNameLength: + ParseHeaderNameLength(data, ref currentIndex, handler); + break; + case State.HeaderNameLengthContinue: + ParseHeaderNameLengthContinue(data, ref currentIndex, handler); + break; + case State.HeaderName: + ParseHeaderName(data, ref currentIndex, handler); + break; + case State.HeaderValueLength: + ParseHeaderValueLength(data, ref currentIndex, handler); + break; + case State.HeaderValueLengthContinue: + ParseHeaderValueLengthContinue(data, ref currentIndex, handler); + break; + case State.HeaderValue: + ParseHeaderValue(data, ref currentIndex, handler); + break; + case State.DynamicTableSizeUpdate: + ParseDynamicTableSizeUpdate(data, ref currentIndex); + break; + default: + // Can't happen + Debug.Fail("HPACK decoder reach an invalid state"); + throw new NotImplementedException(_state.ToString()); + } + } + // Parse methods each check the length. This check is to see whether there is still data available + // and to continue parsing. + while (currentIndex < data.Length); + + // If a header range was set, but the value was not in the data, then copy the range + // to the name buffer. Must copy because because the data will be replaced and the range + // will no longer be valid. + if (_headerNameRange != null) + { + EnsureStringCapacity(ref _headerNameOctets); + _headerName = _headerNameOctets; + + ReadOnlySpan headerBytes = data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length); + headerBytes.CopyTo(_headerName); + _headerNameLength = headerBytes.Length; + _headerNameRange = null; + } + } + + private void ParseDynamicTableSizeUpdate(ReadOnlySpan data, ref int currentIndex) + { + if (TryDecodeInteger(data, ref currentIndex, out int intResult)) + { + SetDynamicHeaderTableSize(intResult); + _state = State.Ready; + } + } + + private void ParseHeaderValueLength(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (currentIndex < data.Length) + { + byte b = data[currentIndex++]; + + _huffman = IsHuffmanEncoded(b); + + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out int intResult)) + { + OnStringLength(intResult, nextState: State.HeaderValue); + + if (intResult == 0) + { + OnString(nextState: State.Ready); + ProcessHeaderValue(data, handler); + } + else + { + ParseHeaderValue(data, ref currentIndex, handler); + } + } + else + { + _state = State.HeaderValueLengthContinue; + ParseHeaderValueLengthContinue(data, ref currentIndex, handler); + } + } + } + + private void ParseHeaderNameLengthContinue(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (TryDecodeInteger(data, ref currentIndex, out int intResult)) + { + // IntegerDecoder disallows overlong encodings, where an integer is encoded with more bytes than is strictly required. + // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. + Debug.Assert(intResult != 0, "A header name length of 0 should never be encoded with a continuation byte."); + + OnStringLength(intResult, nextState: State.HeaderName); + ParseHeaderName(data, ref currentIndex, handler); + } + } + + private void ParseHeaderValueLengthContinue(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (TryDecodeInteger(data, ref currentIndex, out int intResult)) + { + // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. + Debug.Assert(intResult != 0, "A header value length of 0 should never be encoded with a continuation byte."); + + OnStringLength(intResult, nextState: State.HeaderValue); + ParseHeaderValue(data, ref currentIndex, handler); + } + } + + private void ParseHeaderFieldIndex(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (TryDecodeInteger(data, ref currentIndex, out int intResult)) + { + OnIndexedHeaderField(intResult, handler); + } + } + + private void ParseHeaderNameIndex(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (TryDecodeInteger(data, ref currentIndex, out int intResult)) + { + OnIndexedHeaderName(intResult); + ParseHeaderValueLength(data, ref currentIndex, handler); + } + } + + private void ParseHeaderNameLength(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (currentIndex < data.Length) + { + byte b = data[currentIndex++]; + + _huffman = IsHuffmanEncoded(b); + + if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out int intResult)) + { + if (intResult == 0) + { + throw new HPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); + } + + OnStringLength(intResult, nextState: State.HeaderName); + ParseHeaderName(data, ref currentIndex, handler); + } + else + { + _state = State.HeaderNameLengthContinue; + ParseHeaderNameLengthContinue(data, ref currentIndex, handler); + } + } + } + + private void Parse(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + if (currentIndex < data.Length) + { + Debug.Assert(_state == State.Ready, "Should be ready to parse a new header."); + + byte b = data[currentIndex++]; + + switch (BitOperations.LeadingZeroCount(b) - 24) // byte 'b' is extended to uint, so will have 24 extra 0s. + { + case 0: // Indexed Header Field { _headersObserved = true; int val = b & ~IndexedHeaderFieldMask; - if (_integerDecoder.BeginTryDecode((byte)val, IndexedHeaderFieldPrefix, out intResult)) + if (_integerDecoder.BeginTryDecode((byte)val, IndexedHeaderFieldPrefix, out int intResult)) { OnIndexedHeaderField(intResult, handler); } else { _state = State.HeaderFieldIndex; + ParseHeaderFieldIndex(data, ref currentIndex, handler); } + break; } - else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation) - { - _headersObserved = true; - - _index = true; - int val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithIncrementalIndexingPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation) - { - _headersObserved = true; - - _index = false; - int val = b & ~LiteralHeaderFieldWithoutIndexingMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldWithoutIndexingPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation) - { - _headersObserved = true; - - _index = false; - int val = b & ~LiteralHeaderFieldNeverIndexedMask; - - if (val == 0) - { - _state = State.HeaderNameLength; - } - else if (_integerDecoder.BeginTryDecode((byte)val, LiteralHeaderFieldNeverIndexedPrefix, out intResult)) - { - OnIndexedHeaderName(intResult); - } - else - { - _state = State.HeaderNameIndex; - } - } - else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation) + case 1: // Literal Header Field with Incremental Indexing + ParseLiteralHeaderField( + data, + ref currentIndex, + b, + LiteralHeaderFieldWithIncrementalIndexingMask, + LiteralHeaderFieldWithIncrementalIndexingPrefix, + index: true, + handler); + break; + case 4: + default: // Literal Header Field without Indexing + ParseLiteralHeaderField( + data, + ref currentIndex, + b, + LiteralHeaderFieldWithoutIndexingMask, + LiteralHeaderFieldWithoutIndexingPrefix, + index: false, + handler); + break; + case 3: // Literal Header Field Never Indexed + ParseLiteralHeaderField( + data, + ref currentIndex, + b, + LiteralHeaderFieldNeverIndexedMask, + LiteralHeaderFieldNeverIndexedPrefix, + index: false, + handler); + break; + case 2: // Dynamic Table Size Update { // https://tools.ietf.org/html/rfc7541#section-4.2 // This dynamic table size @@ -235,125 +373,107 @@ namespace System.Net.Http.HPack throw new HPackDecodingException(SR.net_http_hpack_late_dynamic_table_size_update); } - if (_integerDecoder.BeginTryDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix, out intResult)) + if (_integerDecoder.BeginTryDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix, out int intResult)) { SetDynamicHeaderTableSize(intResult); } else { _state = State.DynamicTableSizeUpdate; + ParseDynamicTableSizeUpdate(data, ref currentIndex); } + break; } - else - { - // Can't happen - Debug.Fail("Unreachable code"); - throw new InvalidOperationException("Unreachable code."); - } + } + } + } - break; - case State.HeaderFieldIndex: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnIndexedHeaderField(intResult, handler); - } + private void ParseLiteralHeaderField(ReadOnlySpan data, ref int currentIndex, byte b, byte mask, byte indexPrefix, bool index, IHttpHeadersHandler handler) + { + _headersObserved = true; - break; - case State.HeaderNameIndex: - if (_integerDecoder.TryDecode(b, out intResult)) - { - OnIndexedHeaderName(intResult); - } + _index = index; + int val = b & ~mask; - break; - case State.HeaderNameLength: - _huffman = (b & HuffmanMask) != 0; + if (val == 0) + { + _state = State.HeaderNameLength; + ParseHeaderNameLength(data, ref currentIndex, handler); + } + else + { + if (_integerDecoder.BeginTryDecode((byte)val, indexPrefix, out int intResult)) + { + OnIndexedHeaderName(intResult); + ParseHeaderValueLength(data, ref currentIndex, handler); + } + else + { + _state = State.HeaderNameIndex; + ParseHeaderNameIndex(data, ref currentIndex, handler); + } + } + } - if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) - { - if (intResult == 0) - { - throw new HPackDecodingException(SR.Format(SR.net_http_invalid_header_name, "")); - } + private void ParseHeaderName(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + // Read remaining chars, up to the length of the current data + int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); - OnStringLength(intResult, nextState: State.HeaderName); - } - else - { - _state = State.HeaderNameLengthContinue; - } + // Check whether the whole string is available in the data and no decompression required. + // If string is good then mark its range. + // NOTE: it may need to be copied to buffer later the if value is not current data. + if (count == _stringLength && !_huffman) + { + // Fast path. Store the range rather than copying. + _headerNameRange = (start: currentIndex, count); + currentIndex += count; - break; - case State.HeaderNameLengthContinue: - if (_integerDecoder.TryDecode(b, out intResult)) - { - // IntegerDecoder disallows overlong encodings, where an integer is encoded with more bytes than is strictly required. - // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. - Debug.Assert(intResult != 0, "A header name length of 0 should never be encoded with a continuation byte."); + _state = State.HeaderValueLength; + } + else + { + // Copy string to temporary buffer. + // _stringOctets was already + data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); + _stringIndex += count; + currentIndex += count; - OnStringLength(intResult, nextState: State.HeaderName); - } + if (_stringIndex == _stringLength) + { + OnString(nextState: State.HeaderValueLength); + ParseHeaderValueLength(data, ref currentIndex, handler); + } + } + } - break; - case State.HeaderName: - _stringOctets[_stringIndex++] = b; + private void ParseHeaderValue(ReadOnlySpan data, ref int currentIndex, IHttpHeadersHandler handler) + { + // Read remaining chars, up to the length of the current data + int count = Math.Min(_stringLength - _stringIndex, data.Length - currentIndex); - if (_stringIndex == _stringLength) - { - OnString(nextState: State.HeaderValueLength); - } + // Check whether the whole string is available in the data and no decompressed required. + // If string is good then mark its range. + if (count == _stringLength && !_huffman) + { + // Fast path. Store the range rather than copying. + _headerValueRange = (start: currentIndex, count); + currentIndex += count; - break; - case State.HeaderValueLength: - _huffman = (b & HuffmanMask) != 0; + _state = State.Ready; + ProcessHeaderValue(data, handler); + } + else + { + // Copy string to temporary buffer. + data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); + _stringIndex += count; + currentIndex += count; - if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) - { - OnStringLength(intResult, nextState: State.HeaderValue); - - if (intResult == 0) - { - ProcessHeaderValue(handler); - } - } - else - { - _state = State.HeaderValueLengthContinue; - } - - break; - case State.HeaderValueLengthContinue: - if (_integerDecoder.TryDecode(b, out intResult)) - { - // IntegerDecoder disallows overlong encodings where an integer is encoded with more bytes than is strictly required. - // 0 should always be represented by a single byte, so we shouldn't need to check for it in the continuation case. - Debug.Assert(intResult != 0, "A header value length of 0 should never be encoded with a continuation byte."); - - OnStringLength(intResult, nextState: State.HeaderValue); - } - - break; - case State.HeaderValue: - _stringOctets[_stringIndex++] = b; - - if (_stringIndex == _stringLength) - { - ProcessHeaderValue(handler); - } - - break; - case State.DynamicTableSizeUpdate: - if (_integerDecoder.TryDecode(b, out intResult)) - { - SetDynamicHeaderTableSize(intResult); - _state = State.Ready; - } - - break; - default: - // Can't happen - Debug.Fail("HPACK decoder reach an invalid state"); - throw new NotImplementedException(_state.ToString()); + if (_stringIndex == _stringLength) + { + OnString(nextState: State.Ready); + ProcessHeaderValue(data, handler); } } } @@ -371,14 +491,20 @@ namespace System.Net.Http.HPack } } - private void ProcessHeaderValue(IHttpHeadersHandler? handler) + private void ProcessHeaderValue(ReadOnlySpan data, IHttpHeadersHandler handler) { - OnString(nextState: State.Ready); + ReadOnlySpan headerNameSpan = _headerNameRange == null + ? new Span(_headerName, 0, _headerNameLength) + : data.Slice(_headerNameRange.GetValueOrDefault().start, _headerNameRange.GetValueOrDefault().length); - var headerNameSpan = new Span(_headerName, 0, _headerNameLength); - var headerValueSpan = new Span(_headerValueOctets, 0, _headerValueLength); + ReadOnlySpan headerValueSpan = _headerValueRange == null + ? new Span(_headerValueOctets, 0, _headerValueLength) + : data.Slice(_headerValueRange.GetValueOrDefault().start, _headerValueRange.GetValueOrDefault().length); - handler?.OnHeader(headerNameSpan, headerValueSpan); + handler.OnHeader(headerNameSpan, headerValueSpan); + + _headerNameRange = null; + _headerValueRange = null; if (_index) { @@ -395,18 +521,17 @@ namespace System.Net.Http.HPack } } - private void OnIndexedHeaderField(int index, IHttpHeadersHandler? handler) + private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler) { - HeaderField header = GetHeader(index); - handler?.OnHeader(header.Name, header.Value); + ref readonly HeaderField header = ref GetHeader(index); + handler.OnHeader(header.Name, header.Value); _state = State.Ready; } private void OnIndexedHeaderName(int index) { - HeaderField header = GetHeader(index); - _headerName = header.Name; - _headerNameLength = header.Name.Length; + _headerName = GetHeader(index).Name; + _headerNameLength = _headerName.Length; _state = State.HeaderValueLength; } @@ -437,11 +562,7 @@ namespace System.Net.Http.HPack } else { - if (dst.Length < _stringLength) - { - dst = new byte[Math.Max(_stringLength, dst.Length * 2)]; - } - + EnsureStringCapacity(ref dst); Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); return _stringLength; } @@ -467,13 +588,41 @@ namespace System.Net.Http.HPack _state = nextState; } - private HeaderField GetHeader(int index) + private void EnsureStringCapacity(ref byte[] dst) + { + if (dst.Length < _stringLength) + { + dst = new byte[Math.Max(_stringLength, dst.Length * 2)]; + } + } + + private bool TryDecodeInteger(ReadOnlySpan data, ref int currentIndex, out int result) + { + for (; currentIndex < data.Length; currentIndex++) + { + if (_integerDecoder.TryDecode(data[currentIndex], out result)) + { + currentIndex++; + return true; + } + } + + result = default; + return false; + } + + private static bool IsHuffmanEncoded(byte b) + { + return (b & HuffmanMask) != 0; + } + + private ref readonly HeaderField GetHeader(int index) { try { - return index <= H2StaticTable.Count - ? H2StaticTable.Get(index - 1) - : _dynamicTable[index - H2StaticTable.Count - 1]; + return ref index <= H2StaticTable.Count + ? ref H2StaticTable.Get(index - 1) + : ref _dynamicTable[index - H2StaticTable.Count - 1]; } catch (IndexOutOfRangeException) { diff --git a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs index 97cdea1c50..4a8511e682 100644 --- a/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs +++ b/src/Shared/runtime/Http2/Hpack/HPackEncoder.cs @@ -54,7 +54,7 @@ namespace System.Net.Http.HPack case 404: case 500: // Status codes which exist in the HTTP/2 StaticTable. - return EncodeIndexedHeaderField(H2StaticTable.StatusIndex[statusCode], destination, out bytesWritten); + return EncodeIndexedHeaderField(H2StaticTable.GetStatusIndex(statusCode), destination, out bytesWritten); default: // If the status code doesn't have a static index then we need to include the full value. // Write a status index and then the number bytes as a string literal. diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs index ae40406210..ce784c6efc 100644 --- a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs @@ -14,13 +14,13 @@ namespace System.Net.Quic.Implementations.Mock internal sealed class MockConnection : QuicConnectionProvider { private readonly bool _isClient; - private bool _disposed = false; + private bool _disposed; private IPEndPoint? _remoteEndPoint; private IPEndPoint? _localEndPoint; private object _syncObject = new object(); - private Socket? _socket = null; - private IPEndPoint? _peerListenEndPoint = null; - private TcpListener? _inboundListener = null; + private Socket? _socket; + private IPEndPoint? _peerListenEndPoint; + private TcpListener? _inboundListener; private long _nextOutboundBidirectionalStream; private long _nextOutboundUnidirectionalStream; diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs index 88297bdfdd..1ac9d2a095 100644 --- a/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs @@ -13,7 +13,7 @@ namespace System.Net.Quic.Implementations.Mock { internal sealed class MockListener : QuicListenerProvider { - private bool _disposed = false; + private bool _disposed; private SslServerAuthenticationOptions? _sslOptions; private IPEndPoint _listenEndPoint; private TcpListener _tcpListener; diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs index f367d981bd..6e27637e0d 100644 --- a/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs @@ -13,14 +13,14 @@ namespace System.Net.Quic.Implementations.Mock { internal sealed class MockStream : QuicStreamProvider { - private bool _disposed = false; + private bool _disposed; private readonly long _streamId; private bool _canRead; private bool _canWrite; private MockConnection? _connection; - private Socket? _socket = null; + private Socket? _socket; // Constructor for outbound streams internal MockStream(MockConnection connection, long streamId, bool bidirectional) diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs index 0f19506b81..6eeef6c486 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs @@ -6,7 +6,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal { internal sealed class MsQuicSession : IDisposable { - private bool _disposed = false; + private bool _disposed; private IntPtr _nativeObjPtr; private bool _opened; diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs index 6eed08f02d..6f3982491b 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -61,7 +61,7 @@ namespace System.Net.Quic.Implementations.MsQuic // Used by the class to indicate that the stream is writable. private readonly bool _canWrite; - private volatile bool _disposed = false; + private volatile bool _disposed; private List _receiveQuicBuffers = new List(); diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs index 2afdb29901..d5d26ccb37 100644 --- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs +++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; namespace System.Net.Http.Unit.Tests.HPack { - public class HPackDecoderTests : IHttpHeadersHandler + public class HPackDecoderTests { private const int DynamicTableInitialMaxSize = 4096; private const int MaxHeaderFieldSize = 8192; @@ -89,42 +89,26 @@ namespace System.Net.Http.Unit.Tests.HPack private readonly DynamicTable _dynamicTable; private readonly HPackDecoder _decoder; - - private readonly Dictionary _decodedHeaders = new Dictionary(); + private readonly TestHttpHeadersHandler _handler = new TestHttpHeadersHandler(); public HPackDecoderTests() { - _dynamicTable = new DynamicTable(DynamicTableInitialMaxSize); - _decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize, _dynamicTable); + (_dynamicTable, _decoder) = CreateDecoderAndTable(); } - void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) + private static (DynamicTable, HPackDecoder) CreateDecoderAndTable() { - string headerName = Encoding.ASCII.GetString(name); - string headerValue = Encoding.ASCII.GetString(value); + var dynamicTable = new DynamicTable(DynamicTableInitialMaxSize); + var decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize, dynamicTable); - _decodedHeaders[headerName] = headerValue; + return (dynamicTable, decoder); } - void IHttpHeadersHandler.OnStaticIndexedHeader(int index) - { - // Not yet implemented for HPACK. - throw new NotImplementedException(); - } - - void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan value) - { - // Not yet implemented for HPACK. - throw new NotImplementedException(); - } - - void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } - [Fact] public void DecodesIndexedHeaderField_StaticTable() { - _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this); - Assert.Equal("GET", _decodedHeaders[":method"]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: _handler); + Assert.Equal("GET", _handler.DecodedHeaders[":method"]); } [Fact] @@ -134,17 +118,17 @@ namespace System.Net.Http.Unit.Tests.HPack _dynamicTable.Insert(_headerNameBytes, _headerValueBytes); // Index it - _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this); - Assert.Equal(_headerValueString, _decodedHeaders[_headerNameString]); + _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: _handler); + Assert.Equal(_headerValueString, _handler.DecodedHeaders[_headerNameString]); } [Fact] public void DecodesIndexedHeaderField_OutOfRange_Error() { HPackDecodingException exception = Assert.Throws(() => - _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this)); + _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -218,9 +202,9 @@ namespace System.Net.Http.Unit.Tests.HPack // 11 1110 (Indexed Name - Index 62 encoded with 6-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -294,9 +278,9 @@ namespace System.Net.Http.Unit.Tests.HPack // 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -310,6 +294,19 @@ namespace System.Net.Http.Unit.Tests.HPack TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString); } + [Fact] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Duplicated() + { + byte[] encoded = _literalHeaderFieldNeverIndexedNewName + .Concat(_headerName) + .Concat(_headerValue) + .ToArray(); + + encoded = encoded.Concat(encoded).ToArray(); + + TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString); + } + [Fact] public void DecodesLiteralHeaderFieldNeverIndexed_NewName_HuffmanEncodedName() { @@ -376,9 +373,9 @@ namespace System.Net.Http.Unit.Tests.HPack // 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) // Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw. - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_hpack_invalid_index, 62), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -389,10 +386,10 @@ namespace System.Net.Http.Unit.Tests.HPack Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this); + _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: _handler); Assert.Equal(30, _dynamicTable.MaxSize); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -404,7 +401,7 @@ namespace System.Net.Http.Unit.Tests.HPack Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); byte[] data = _indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(); - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: _handler)); Assert.Equal(SR.net_http_hpack_late_dynamic_table_size_update, exception.Message); } @@ -413,13 +410,13 @@ namespace System.Net.Http.Unit.Tests.HPack { Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: this); - Assert.Equal("GET", _decodedHeaders[":method"]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: _handler); + Assert.Equal("GET", _handler.DecodedHeaders[":method"]); // 001 (Dynamic Table Size Update) // 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) byte[] data = new byte[] { 0x3e }; - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(data, endHeaders: true, handler: _handler)); Assert.Equal(SR.net_http_hpack_late_dynamic_table_size_update, exception.Message); } @@ -428,12 +425,12 @@ namespace System.Net.Http.Unit.Tests.HPack { Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); - _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this); - Assert.Equal("GET", _decodedHeaders[":method"]); + _decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: _handler); + Assert.Equal("GET", _handler.DecodedHeaders[":method"]); // 001 (Dynamic Table Size Update) // 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation) - _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this); + _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: _handler); Assert.Equal(30, _dynamicTable.MaxSize); } @@ -447,9 +444,9 @@ namespace System.Net.Http.Unit.Tests.HPack Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize); HPackDecodingException exception = Assert.Throws(() => - _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: this)); + _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_hpack_large_table_size_update, 4097, DynamicTableInitialMaxSize), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -459,9 +456,9 @@ namespace System.Net.Http.Unit.Tests.HPack .Concat(new byte[] { 0xff, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix .ToArray(); - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler)); Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, MaxHeaderFieldSize), exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } [Fact] @@ -477,9 +474,57 @@ namespace System.Net.Http.Unit.Tests.HPack .Concat(Encoding.ASCII.GetBytes(string8193)) .ToArray(); - decoder.Decode(encoded, endHeaders: true, handler: this); + decoder.Decode(encoded, endHeaders: true, handler: _handler); - Assert.Equal(string8193, _decodedHeaders[string8193]); + Assert.Equal(string8193, _handler.DecodedHeaders[string8193]); + } + + [Fact] + public void DecodesStringLength_IndividualBytes() + { + HPackDecoder decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize + 1); + string string8193 = new string('a', MaxHeaderFieldSize + 1); + + byte[] encoded = _literalHeaderFieldWithoutIndexingNewName + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .ToArray(); + + for (int i = 0; i < encoded.Length; i++) + { + bool end = i + 1 == encoded.Length; + + decoder.Decode(new byte[] { encoded[i] }, endHeaders: end, handler: _handler); + } + + Assert.Equal(string8193, _handler.DecodedHeaders[string8193]); + } + + [Fact] + public void DecodesHeaderNameAndValue_SeparateSegments() + { + HPackDecoder decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxHeaderFieldSize + 1); + string string8193 = new string('a', MaxHeaderFieldSize + 1); + + byte[][] segments = new byte[][] + { + _literalHeaderFieldWithoutIndexingNewName, + new byte[] { 0x7f, 0x82, 0x3f }, // 8193 encoded with 7-bit prefix, no Huffman encoding + Encoding.ASCII.GetBytes(string8193), + new byte[] { 0x7f, 0x82, 0x3f }, // 8193 encoded with 7-bit prefix, no Huffman encoding + Encoding.ASCII.GetBytes(string8193) + }; + + for (int i = 0; i < segments.Length; i++) + { + bool end = i + 1 == segments.Length; + + decoder.Decode(segments[i], endHeaders: end, handler: _handler); + } + + Assert.Equal(string8193, _handler.DecodedHeaders[string8193]); } public static readonly TheoryData _incompleteHeaderBlockData = new TheoryData @@ -567,9 +612,9 @@ namespace System.Net.Http.Unit.Tests.HPack [MemberData(nameof(_incompleteHeaderBlockData))] public void DecodesIncompleteHeaderBlock_Error(byte[] encoded) { - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler)); Assert.Equal(SR.net_http_hpack_incomplete_header_block, exception.Message); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } public static readonly TheoryData _huffmanDecodingErrorData = new TheoryData @@ -601,43 +646,89 @@ namespace System.Net.Http.Unit.Tests.HPack [MemberData(nameof(_huffmanDecodingErrorData))] public void WrapsHuffmanDecodingExceptionInHPackDecodingException(byte[] encoded) { - HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: this)); + HPackDecodingException exception = Assert.Throws(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler)); Assert.Equal(SR.net_http_hpack_huffman_decode_failed, exception.Message); Assert.IsType(exception.InnerException); - Assert.Empty(_decodedHeaders); + Assert.Empty(_handler.DecodedHeaders); } - private void TestDecodeWithIndexing(byte[] encoded, string expectedHeaderName, string expectedHeaderValue) + private static void TestDecodeWithIndexing(byte[] encoded, string expectedHeaderName, string expectedHeaderValue) { - TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: true); + TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: true, byteAtATime: false); + TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: true, byteAtATime: true); } - private void TestDecodeWithoutIndexing(byte[] encoded, string expectedHeaderName, string expectedHeaderValue) + private static void TestDecodeWithoutIndexing(byte[] encoded, string expectedHeaderName, string expectedHeaderValue) { - TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: false); + TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: false, byteAtATime: false); + TestDecode(encoded, expectedHeaderName, expectedHeaderValue, expectDynamicTableEntry: false, byteAtATime: true); } - private void TestDecode(byte[] encoded, string expectedHeaderName, string expectedHeaderValue, bool expectDynamicTableEntry) + private static void TestDecode(byte[] encoded, string expectedHeaderName, string expectedHeaderValue, bool expectDynamicTableEntry, bool byteAtATime) { - Assert.Equal(0, _dynamicTable.Count); - Assert.Equal(0, _dynamicTable.Size); + var (dynamicTable, decoder) = CreateDecoderAndTable(); + var handler = new TestHttpHeadersHandler(); - _decoder.Decode(encoded, endHeaders: true, handler: this); + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); - Assert.Equal(expectedHeaderValue, _decodedHeaders[expectedHeaderName]); - - if (expectDynamicTableEntry) + if (!byteAtATime) { - Assert.Equal(1, _dynamicTable.Count); - Assert.Equal(expectedHeaderName, Encoding.ASCII.GetString(_dynamicTable[0].Name)); - Assert.Equal(expectedHeaderValue, Encoding.ASCII.GetString(_dynamicTable[0].Value)); - Assert.Equal(expectedHeaderName.Length + expectedHeaderValue.Length + 32, _dynamicTable.Size); + decoder.Decode(encoded, endHeaders: true, handler: handler); } else { - Assert.Equal(0, _dynamicTable.Count); - Assert.Equal(0, _dynamicTable.Size); + // Parse data in 1 byte chunks, separated by empty chunks + for (int i = 0; i < encoded.Length; i++) + { + bool end = i + 1 == encoded.Length; + + decoder.Decode(Array.Empty(), endHeaders: false, handler: handler); + decoder.Decode(new byte[] { encoded[i] }, endHeaders: end, handler: handler); + } + } + + Assert.Equal(expectedHeaderValue, handler.DecodedHeaders[expectedHeaderName]); + + if (expectDynamicTableEntry) + { + Assert.Equal(1, dynamicTable.Count); + Assert.Equal(expectedHeaderName, Encoding.ASCII.GetString(dynamicTable[0].Name)); + Assert.Equal(expectedHeaderValue, Encoding.ASCII.GetString(dynamicTable[0].Value)); + Assert.Equal(expectedHeaderName.Length + expectedHeaderValue.Length + 32, dynamicTable.Size); + } + else + { + Assert.Equal(0, dynamicTable.Count); + Assert.Equal(0, dynamicTable.Size); } } } + + public class TestHttpHeadersHandler : IHttpHeadersHandler + { + public Dictionary DecodedHeaders { get; } = new Dictionary(); + + void IHttpHeadersHandler.OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + string headerName = Encoding.ASCII.GetString(name); + string headerValue = Encoding.ASCII.GetString(value); + + DecodedHeaders[headerName] = headerValue; + } + + void IHttpHeadersHandler.OnStaticIndexedHeader(int index) + { + // Not yet implemented for HPACK. + throw new NotImplementedException(); + } + + void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + // Not yet implemented for HPACK. + throw new NotImplementedException(); + } + + void IHttpHeadersHandler.OnHeadersComplete(bool endStream) { } + } } diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs index fc24655655..1a108482a1 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnectionExtensions.cs @@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.SignalR.Client ///
public static partial class HubConnectionExtensions { - private static IDisposable On(this HubConnection hubConnetion, string methodName, Type[] parameterTypes, Action handler) + private static IDisposable On(this HubConnection hubConnection, string methodName, Type[] parameterTypes, Action handler) { - return hubConnetion.On(methodName, parameterTypes, (parameters, state) => + return hubConnection.On(methodName, parameterTypes, (parameters, state) => { var currentHandler = (Action)state; currentHandler(parameters); diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 39f99f7ff0..1e1de9abe4 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CheckFixedMessage(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols["json"]; - using (var server = await StartServer(ExpectedError)) + await using (var server = await StartServer(ExpectedError)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ClientCanConnectToServerWithLowerMinimumProtocol() { var protocol = HubProtocols["json"]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanSendAndReceiveMessage(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanSendNull(string protocolName) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, protocol, LoggerFactory); try @@ -243,7 +243,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStopAndStartConnection(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanAccessConnectionIdFromHubConnection(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -310,7 +310,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStartConnectionFromClosedEvent(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); const string originalMessage = "SignalR"; @@ -372,7 +372,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task MethodsAreCaseInsensitive(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { const string originalMessage = "SignalR"; var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); @@ -402,7 +402,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanInvokeFromOnHandler(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { const string originalMessage = "SignalR"; @@ -442,7 +442,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCoreTest(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToHubWithIAsyncEnumerableMethodArg(string protocolName) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.WebSockets, protocol, LoggerFactory); try @@ -523,7 +523,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncTest(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -558,7 +558,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncDoesNotStartIfTokenAlreadyCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCanBeCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -643,7 +643,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols[protocolName]; - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -679,7 +679,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanInvokeClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { const string originalMessage = "SignalR"; @@ -713,7 +713,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task InvokeNonExistantClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); var closeTcs = new TaskCompletionSource(); @@ -755,7 +755,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamClientMethodFromServer(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -785,7 +785,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToAndFromClientInSameInvocation(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -822,7 +822,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanStreamToServerWithIAsyncEnumerable(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -869,7 +869,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanCancelIAsyncEnumerableClientToServerUpload(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -922,7 +922,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamAsyncCanBeCanceledThroughGetAsyncEnumerator(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -963,7 +963,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task CanCloseStreamMethodEarly(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1004,7 +1004,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task StreamDoesNotStartIfTokenAlreadyCanceled(string protocolName, HttpTransportType transportType, string path) { var protocol = HubProtocols[protocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1039,7 +1039,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } var protocol = HubProtocols[protocolName]; - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection(server.Url, path, transportType, protocol, LoggerFactory); try @@ -1067,7 +1067,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1094,7 +1094,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolvedAndArgumentsPassedIn(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1121,7 +1121,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch(string hubProtocolName) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, hubProtocol, LoggerFactory); try @@ -1148,7 +1148,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1175,7 +1175,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1203,7 +1203,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1231,7 +1231,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1259,7 +1259,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsync(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1286,7 +1286,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1313,7 +1313,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests public async Task ServerThrowsHubExceptionIfBuildingAsyncEnumeratorIsNotPossible(string hubProtocolName, HttpTransportType transportType, string hubPath) { var hubProtocol = HubProtocols[hubProtocolName]; - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); try @@ -1349,7 +1349,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests }; var protocol = HubProtocols[protocolName]; - using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) + await using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.WebSockets, protocol, LoggerFactory); var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -1396,7 +1396,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests }; var protocol = HubProtocols[protocolName]; - using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) + await using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, protocol, LoggerFactory); var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -1436,7 +1436,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var hubProtocol = HubProtocols.First().Value; var transportType = TransportTypes().First().Cast().First(); - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connection = CreateHubConnection(server.Url, hubPath, transportType, hubProtocol, LoggerFactory); await connection.StartAsync().OrTimeout(); @@ -1453,7 +1453,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanUseJwtBearerTokenForAuthentication(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { async Task AccessTokenProvider() { @@ -1496,7 +1496,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests return writeContext.Exception is HttpRequestException; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1518,7 +1518,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanUseJwtBearerTokenForAuthenticationWhenRedirected(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1546,7 +1546,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientCanSendHeaders(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1577,7 +1577,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task UserAgentIsSet() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1619,7 +1619,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task UserAgentCanBeCleared() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1654,7 +1654,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task UserAgentCanBeSet() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1690,7 +1690,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [WebSocketsSupportedCondition] public async Task WebSocketOptionsAreApplied() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { // System.Net has a HttpTransportType type which means we need to fully-qualify this rather than 'use' the namespace var cookieJar = new System.Net.CookieContainer(); @@ -1724,7 +1724,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task CheckHttpConnectionFeatures() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1760,7 +1760,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task UserIdProviderCanAccessHttpContext() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1791,7 +1791,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task NegotiationSkipsServerSentEventsWhenUsingBinaryProtocol() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var hubConnectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1821,7 +1821,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [Fact] public async Task StopCausesPollToReturnImmediately() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { PollTrackingMessageHandler pollTracker = null; var hubConnection = new HubConnectionBuilder() @@ -1869,7 +1869,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connection = CreateHubConnection( server.Url, @@ -1931,7 +1931,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connection = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -1991,7 +1991,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests writeContext.EventId.Name == "ReconnectingWithError"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs index a7240053b8..fbf5ea3fd6 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubProtocolVersionTests.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingOldCallWithOriginalProtocol(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingOldCallWithNewProtocol(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(TransportTypes))] public async Task ClientUsingNewCallWithNewProtocol(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var httpConnectionFactory = new HttpConnectionFactory( Options.Create(new HttpConnectionOptions @@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests return writeContext.LoggerName == typeof(HubConnection).FullName; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(LoggerFactory) diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TimerAwaitableTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TimerAwaitableTests.cs index e0e7248df4..869626d7cd 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TimerAwaitableTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TimerAwaitableTests.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public class TimerAwaitableTests { [Fact] - [QuarantinedTest] public async Task FinalizerRunsIfTimerAwaitableReferencesObject() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); diff --git a/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj index 6a1851569e..84771b3fe4 100644 --- a/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj +++ b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj @@ -78,7 +78,7 @@ - $(GradleOptions) -PpackageVersion="$(PackageVersion)" + $(GradleOptions) -PpackageVersion="$(PackageVersion)" chmod +x ./gradlew && ./gradlew $(GradleOptions) test call gradlew $(GradleOptions) test 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 index 4c73a498f6..3877f515be 100644 --- 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 @@ -6,6 +6,6 @@ package com.microsoft.signalr; class Version { public static String getDetailedVersion() { - return "99.99.99-dev"; + return "5.0.0-dev"; } } diff --git a/src/SignalR/clients/ts/FunctionalTests/Program.cs b/src/SignalR/clients/ts/FunctionalTests/Program.cs index 2f67e3d5cd..bc1cc0a323 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Program.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Program.cs @@ -5,7 +5,9 @@ using System; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Win32; @@ -13,7 +15,7 @@ namespace FunctionalTests { public class Program { - public static void Main(string[] args) + public static Task Main(string[] args) { string url = null; for (var i = 0; i < args.Length; i++) @@ -27,65 +29,69 @@ namespace FunctionalTests } } - var hostBuilder = new WebHostBuilder() - .ConfigureLogging(factory => + var hostBuilder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => { - factory.AddConsole(options => + webHostBuilder + .ConfigureLogging(factory => { - options.IncludeScopes = true; - options.TimestampFormat = "[HH:mm:ss] "; - options.UseUtcTimestamp = true; - }); - factory.AddDebug(); - factory.SetMinimumLevel(LogLevel.Debug); - }) - .UseKestrel((builderContext, options) => - { - options.ConfigureHttpsDefaults(httpsOptions => - { - bool useRSA = false; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + factory.AddConsole(options => { - // Detect Win10+ - var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); - var major = key.GetValue("CurrentMajorVersionNumber") as int?; - var minor = key.GetValue("CurrentMinorVersionNumber") as int?; + options.IncludeScopes = true; + options.TimestampFormat = "[HH:mm:ss] "; + options.UseUtcTimestamp = true; + }); + factory.AddDebug(); + factory.SetMinimumLevel(LogLevel.Debug); + }) + .UseKestrel((builderContext, options) => + { + options.ConfigureHttpsDefaults(httpsOptions => + { + bool useRSA = false; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Detect Win10+ + var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + var major = key.GetValue("CurrentMajorVersionNumber") as int?; + var minor = key.GetValue("CurrentMinorVersionNumber") as int?; - if (major.HasValue && minor.HasValue) + if (major.HasValue && minor.HasValue) + { + useRSA = true; + } + } + else { useRSA = true; } - } - else - { - useRSA = true; - } - if (useRSA) - { - // RSA cert, won't work on Windows 8.1 & Windows 2012 R2 using HTTP2, and ECC won't work in some Node environments - var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCert.pfx"); - httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); - } - else - { - // ECC cert, works on Windows 8.1 & Windows 2012 R2 using HTTP2 - var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCertECC.pfx"); - httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); - } - }); - }) - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup(); + if (useRSA) + { + // RSA cert, won't work on Windows 8.1 & Windows 2012 R2 using HTTP2, and ECC won't work in some Node environments + var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCert.pfx"); + httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); + } + else + { + // ECC cert, works on Windows 8.1 & Windows 2012 R2 using HTTP2 + var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCertECC.pfx"); + httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); + } + }); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup(); - if (!string.IsNullOrEmpty(url)) - { - Console.WriteLine($"Forcing URL to: {url}"); - hostBuilder.UseUrls(url); - } + if (!string.IsNullOrEmpty(url)) + { + Console.WriteLine($"Forcing URL to: {url}"); + webHostBuilder.UseUrls(url); + } + }); - hostBuilder.Build().Run(); + return hostBuilder.Build().RunAsync(); } } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs index 783bcf07bf..5958c7ef92 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.Http.Connections.Internal.Transports; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Http.Connections.Internal @@ -29,7 +30,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal ITransferFormatFeature, IHttpContextFeature, IHttpTransportFeature, - IConnectionInherentKeepAliveFeature + IConnectionInherentKeepAliveFeature, + IConnectionLifetimeFeature { private static long _tenSeconds = TimeSpan.FromSeconds(10).Ticks; @@ -41,6 +43,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private PipeWriterStream _applicationStream; private IDuplexPipe _application; private IDictionary _items; + private CancellationTokenSource _connectionClosedTokenSource; private CancellationTokenSource _sendCts; private bool _activeSend; @@ -82,6 +85,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal Features.Set(this); Features.Set(this); Features.Set(this); + Features.Set(this); + + _connectionClosedTokenSource = new CancellationTokenSource(); + ConnectionClosed = _connectionClosedTokenSource.Token; } public CancellationTokenSource Cancellation { get; set; } @@ -93,6 +100,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal // Used for testing only internal Task DisposeAndRemoveTask { get; set; } + // Used for LongPolling because we need to create a scope that spans the lifetime of multiple requests on the cloned HttpContext + internal IServiceScope ServiceScope { get; set; } + public Task TransportTask { get; set; } public Task PreviousPollTask { get; set; } = Task.CompletedTask; @@ -170,6 +180,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal public HttpContext HttpContext { get; set; } + public override CancellationToken ConnectionClosed { get; set; } + + public override void Abort() + { + ThreadPool.UnsafeQueueUserWorkItem(cts => ((CancellationTokenSource)cts).Cancel(), _connectionClosedTokenSource); + + HttpContext?.Abort(); + } + public void OnHeartbeat(Action action, object state) { lock (_heartbeatLock) @@ -236,6 +255,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal (identity as IDisposable)?.Dispose(); } } + + ServiceScope?.Dispose(); } await disposeTask; @@ -305,6 +326,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal // Now complete the application Application?.Output.Complete(); Application?.Input.Complete(); + + // Trigger ConnectionClosed + ThreadPool.UnsafeQueueUserWorkItem(cts => ((CancellationTokenSource)cts).Cancel(), _connectionClosedTokenSource); } } else @@ -313,6 +337,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal Application?.Output.Complete(transportTask.Exception?.InnerException); Application?.Input.Complete(); + // Trigger ConnectionClosed + ThreadPool.UnsafeQueueUserWorkItem(cts => ((CancellationTokenSource)cts).Cancel(), _connectionClosedTokenSource); + try { // A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs index 7f6477cab0..fab15305ce 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections.Internal.Transports; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -537,8 +538,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal var existing = connection.HttpContext; if (existing == null) { - var httpContext = CloneHttpContext(context); - connection.HttpContext = httpContext; + CloneHttpContext(context, connection); } else { @@ -606,7 +606,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal } } - private static HttpContext CloneHttpContext(HttpContext context) + private static void CloneHttpContext(HttpContext context, HttpConnectionContext connection) { // The reason we're copying the base features instead of the HttpContext properties is // so that we can get all of the logic built into DefaultHttpContext to extract higher level @@ -660,14 +660,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal CloneUser(newHttpContext, context); - // Making request services function property could be tricky and expensive as it would require - // DI scope per connection. It would also mean that services resolved in middleware leading up to here - // wouldn't be the same instance (but maybe that's fine). For now, we just return an empty service provider - newHttpContext.RequestServices = EmptyServiceProvider.Instance; + connection.ServiceScope = context.RequestServices.CreateScope(); + newHttpContext.RequestServices = connection.ServiceScope.ServiceProvider; // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items newHttpContext.Items = new Dictionary(context.Items); - return newHttpContext; + + connection.HttpContext = newHttpContext; } private async Task GetConnectionAsync(HttpContext context) diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 22e5d5d6a2..0db4c75850 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -510,6 +510,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; + context.RequestServices = services.BuildServiceProvider(); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.Use(next => @@ -723,6 +724,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests context.Connection.RemoteIpAddress = IPAddress.IPv6Any; context.Connection.RemotePort = 43456; context.SetEndpoint(new Endpoint(null, null, "TestName")); + context.RequestServices = services.BuildServiceProvider(); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); @@ -942,12 +944,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); + var services = new ServiceCollection(); + services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); SetTransport(context, HttpTransportType.ServerSentEvents); - var services = new ServiceCollection(); - services.AddSingleton(); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -961,7 +963,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } [Fact] - public async Task SynchronusExceptionEndsConnection() + public async Task SynchronousExceptionEndsConnection() { bool ExpectedErrors(WriteContext writeContext) { @@ -976,11 +978,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests connection.TransportType = HttpTransportType.ServerSentEvents; var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, HttpTransportType.ServerSentEvents); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, HttpTransportType.ServerSentEvents); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1004,10 +1006,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1035,10 +1037,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1128,9 +1130,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var connection = manager.CreateConnection(); connection.TransportType = HttpTransportType.LongPolling; var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1161,10 +1163,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var connection = manager.CreateConnection(); connection.TransportType = HttpTransportType.ServerSentEvents; var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, connection.TransportType); var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, connection.TransportType); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1199,10 +1201,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests connection.TransportType = HttpTransportType.WebSockets; var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); var sync = new SyncPoint(); - var context = MakeRequest("/foo", connection); - SetTransport(context, connection.TransportType, sync); var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, connection.TransportType, sync); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1232,11 +1234,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, HttpTransportType.WebSockets); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, HttpTransportType.WebSockets); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1262,14 +1264,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context1 = MakeRequest("/foo", connection); - var context2 = MakeRequest("/foo", connection); + var services = new ServiceCollection(); + services.AddSingleton(); + var context1 = MakeRequest("/foo", connection, services); + var context2 = MakeRequest("/foo", connection, services); SetTransport(context1, transportType); SetTransport(context2, transportType); - var services = new ServiceCollection(); - services.AddSingleton(); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1305,11 +1307,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context1 = MakeRequest("/foo", connection); - var context2 = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context1 = MakeRequest("/foo", connection, services); + var context2 = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1369,11 +1371,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context1 = MakeRequest("/foo", connection); - var context2 = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context1 = MakeRequest("/foo", connection, services); + var context2 = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1432,11 +1434,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, transportType); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, transportType); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1459,10 +1461,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1494,11 +1496,11 @@ 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(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, HttpTransportType.ServerSentEvents); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1529,10 +1531,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1577,12 +1579,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var app = builder.Build(); var options = new HttpConnectionDispatcherOptions(); - var context1 = MakeRequest("/foo", connection); + var context1 = MakeRequest("/foo", connection, services); // This is the initial poll to make sure things are setup var task1 = dispatcher.ExecuteAsync(context1, options, app); Assert.True(task1.IsCompleted); task1 = dispatcher.ExecuteAsync(context1, options, app); - var context2 = MakeRequest("/foo", connection); + var context2 = MakeRequest("/foo", connection, services); var task2 = dispatcher.ExecuteAsync(context2, options, app); // Task 1 should finish when request 2 arrives @@ -1615,11 +1617,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, transportType); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, transportType); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1751,10 +1753,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1783,11 +1785,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - SetTransport(context, transportType); - var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(); + var context = MakeRequest("/foo", connection, serviceCollection); + SetTransport(context, transportType); + var services = serviceCollection.BuildServiceProvider(); var builder = new ConnectionBuilder(services); builder.UseConnectionHandler(); @@ -1824,10 +1826,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1877,10 +1879,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1926,10 +1928,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); - var context = MakeRequest("/foo", connection); - var services = new ServiceCollection(); services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); var app = builder.Build(); @@ -1992,7 +1994,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests }; { var options = new HttpConnectionDispatcherOptions(); - var context = MakeRequest("/foo", connection); + var context = MakeRequest("/foo", connection, new ServiceCollection()); await dispatcher.ExecuteAsync(context, options, connectionDelegate).OrTimeout(); // second poll should have data @@ -2008,7 +2010,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests waitForMessageTcs2.SetResult(); await messageTcs2.Task.OrTimeout(); - context = MakeRequest("/foo", connection); + context = MakeRequest("/foo", connection, new ServiceCollection()); ms.Seek(0, SeekOrigin.Begin); context.Response.Body = ms; // This is the third poll which gets the final message after the app is complete @@ -2217,7 +2219,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var sendTask = dispatcher.ExecuteAsync(context, options, app).OrTimeout(); Assert.False(sendTask.IsCompleted); - var pollContext = MakeRequest("/foo", connection); + var pollContext = MakeRequest("/foo", connection, services); // This should unblock the send that is waiting because of backpressure // Testing deadlock regression where pipe backpressure would hold the same lock that poll would use await dispatcher.ExecuteAsync(pollContext, options, app).OrTimeout(); @@ -2253,12 +2255,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var app = builder.Build(); var options = new HttpConnectionDispatcherOptions(); - var context = MakeRequest("/foo", connection); + var context = MakeRequest("/foo", connection, services); // Initial poll will complete immediately await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); - var pollContext = MakeRequest("/foo", connection); + var pollContext = MakeRequest("/foo", connection, services); var pollTask = dispatcher.ExecuteAsync(pollContext, options, app); // fail LongPollingTransport ReadAsync connection.Transport.Output.Complete(new InvalidOperationException()); @@ -2269,6 +2271,312 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } + [Fact] + public async Task LongPollingConnectionClosingTriggersConnectionClosedToken() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var pipeOptions = new PipeOptions(pauseWriterThreshold: 2, resumeWriterThreshold: 1); + var connection = manager.CreateConnection(pipeOptions, pipeOptions); + connection.TransportType = HttpTransportType.LongPolling; + + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + + var services = new ServiceCollection(); + services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + + var pollTask = dispatcher.ExecuteAsync(context, options, app); + Assert.True(pollTask.IsCompleted); + + // Now send the second poll + pollTask = dispatcher.ExecuteAsync(context, options, app); + + // 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.Method = "DELETE"; + + Assert.False(pollTask.IsCompleted); + + await dispatcher.ExecuteAsync(deleteContext, options, app).OrTimeout(); + + await pollTask.OrTimeout(); + + // Verify that transport shuts down + await connection.TransportTask.OrTimeout(); + + // Verify the response from the DELETE request + Assert.Equal(StatusCodes.Status202Accepted, deleteContext.Response.StatusCode); + Assert.Equal("text/plain", deleteContext.Response.ContentType); + Assert.Equal(HttpConnectionStatus.Disposed, connection.Status); + + await connection.ConnectionClosed.WaitForCancellationAsync().OrTimeout(); + + // Verify the connection not removed because application is hanging + Assert.True(manager.TryGetConnection(connection.ConnectionId, out _)); + } + } + + [Fact] + public async Task SSEConnectionClosingTriggersConnectionClosedToken() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.ServerSentEvents; + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var services = new ServiceCollection(); + services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, connection.TransportType); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + _ = dispatcher.ExecuteAsync(context, options, app); + + // Close the SSE connection + connection.Transport.Output.Complete(); + + await connection.ConnectionClosed.WaitForCancellationAsync().OrTimeout(); + } + } + + [Fact] + public async Task WebSocketConnectionClosingTriggersConnectionClosedToken() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.WebSockets; + + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var services = new ServiceCollection(); + services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + SetTransport(context, HttpTransportType.WebSockets); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + options.WebSockets.CloseTimeout = TimeSpan.FromSeconds(1); + + _ = dispatcher.ExecuteAsync(context, options, app); + + var websocket = (TestWebSocketConnectionFeature)context.Features.Get(); + await websocket.Accepted.OrTimeout(); + await websocket.Client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", cancellationToken: default).OrTimeout(); + + await connection.ConnectionClosed.WaitForCancellationAsync().OrTimeout(); + } + } + + public class CustomHttpRequestLifetimeFeature : IHttpRequestLifetimeFeature + { + public CancellationToken RequestAborted { get; set; } + + private CancellationTokenSource _cts; + public CustomHttpRequestLifetimeFeature() + { + _cts = new CancellationTokenSource(); + RequestAborted = _cts.Token; + } + + public void Abort() + { + _cts.Cancel(); + } + } + + [Fact] + public async Task AbortingConnectionAbortsHttpContextAndTriggersConnectionClosedToken() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.ServerSentEvents; + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + var services = new ServiceCollection(); + services.AddSingleton(); + var context = MakeRequest("/foo", connection, services); + var lifetimeFeature = new CustomHttpRequestLifetimeFeature(); + context.Features.Set(lifetimeFeature); + SetTransport(context, connection.TransportType); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + _ = dispatcher.ExecuteAsync(context, options, app); + + connection.Abort(); + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + connection.ConnectionClosed.Register(() => tcs.SetResult(null)); + await tcs.Task.OrTimeout(); + + tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + lifetimeFeature.RequestAborted.Register(() => tcs.SetResult(null)); + await tcs.Task.OrTimeout(); + } + } + + [Fact] + public async Task ServicesAvailableWithLongPolling() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.LongPolling; + + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(new MessageWrapper() { Buffer = new ReadOnlySequence(new byte[] { 1, 2, 3 }) }); + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + + var context = MakeRequest("/foo", connection, services); + + // Initial poll will complete immediately + await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); + + var pollContext = MakeRequest("/foo", connection, services); + var pollTask = dispatcher.ExecuteAsync(pollContext, options, app); + + await connection.Application.Output.WriteAsync(new byte[] { 1 }).OrTimeout(); + await pollTask.OrTimeout(); + + var memory = new Memory(new byte[10]); + pollContext.Response.Body.Position = 0; + Assert.Equal(3, await pollContext.Response.Body.ReadAsync(memory).OrTimeout()); + Assert.Equal(new byte[] { 1, 2, 3 }, memory.Slice(0, 3).ToArray()); + + // Connection will use the original service provider so this will have no effect + services.AddSingleton(new MessageWrapper() { Buffer = new ReadOnlySequence(new byte[] { 4, 5, 6 }) }); + pollContext = MakeRequest("/foo", connection, services); + pollTask = dispatcher.ExecuteAsync(pollContext, options, app); + + await connection.Application.Output.WriteAsync(new byte[] { 1 }).OrTimeout(); + await pollTask.OrTimeout(); + + pollContext.Response.Body.Position = 0; + Assert.Equal(3, await pollContext.Response.Body.ReadAsync(memory).OrTimeout()); + Assert.Equal(new byte[] { 1, 2, 3 }, memory.Slice(0, 3).ToArray()); + + await connection.DisposeAsync().OrTimeout(); + } + } + + [Fact] + public async Task ServicesPreserveScopeWithLongPolling() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.LongPolling; + + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + + var services = new ServiceCollection(); + services.AddSingleton(); + var iteration = 0; + services.AddScoped(typeof(MessageWrapper), _ => + { + iteration++; + return new MessageWrapper() { Buffer = new ReadOnlySequence(new byte[] { (byte)(iteration + 1), (byte)(iteration + 2), (byte)(iteration + 3) }) }; + }); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + + var context = MakeRequest("/foo", connection, services); + + // Initial poll will complete immediately + await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); + + var pollContext = MakeRequest("/foo", connection, services); + var pollTask = dispatcher.ExecuteAsync(pollContext, options, app); + + await connection.Application.Output.WriteAsync(new byte[] { 1 }).OrTimeout(); + await pollTask.OrTimeout(); + + var memory = new Memory(new byte[10]); + pollContext.Response.Body.Position = 0; + Assert.Equal(3, await pollContext.Response.Body.ReadAsync(memory).OrTimeout()); + Assert.Equal(new byte[] { 2, 3, 4 }, memory.Slice(0, 3).ToArray()); + + pollContext = MakeRequest("/foo", connection, services); + pollTask = dispatcher.ExecuteAsync(pollContext, options, app); + + await connection.Application.Output.WriteAsync(new byte[] { 1 }).OrTimeout(); + await pollTask.OrTimeout(); + + pollContext.Response.Body.Position = 0; + Assert.Equal(3, await pollContext.Response.Body.ReadAsync(memory).OrTimeout()); + Assert.Equal(new byte[] { 2, 3, 4 }, memory.Slice(0, 3).ToArray()); + + await connection.DisposeAsync().OrTimeout(); + } + } + + [Fact] + public async Task DisposeLongPollingConnectionDisposesServiceScope() + { + using (StartVerifiableLog()) + { + var manager = CreateConnectionManager(LoggerFactory); + var connection = manager.CreateConnection(); + connection.TransportType = HttpTransportType.LongPolling; + + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); + + var services = new ServiceCollection(); + services.AddSingleton(); + var iteration = 0; + services.AddScoped(typeof(MessageWrapper), _ => + { + iteration++; + return new MessageWrapper() { Buffer = new ReadOnlySequence(new byte[] { (byte)(iteration + 1), (byte)(iteration + 2), (byte)(iteration + 3) }) }; + }); + + var builder = new ConnectionBuilder(services.BuildServiceProvider()); + builder.UseConnectionHandler(); + var app = builder.Build(); + var options = new HttpConnectionDispatcherOptions(); + + var context = MakeRequest("/foo", connection, services); + + // Initial poll will complete immediately + await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); + + // ServiceScope will be disposed here + await connection.DisposeAsync().OrTimeout(); + + Assert.Throws(() => connection.ServiceScope.ServiceProvider.GetService()); + } + } + private static async Task CheckTransportSupported(HttpTransportType supportedTransports, HttpTransportType transportType, int status, ILoggerFactory loggerFactory) { var manager = CreateConnectionManager(loggerFactory); @@ -2292,6 +2600,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests values["negotiateVersion"] = "1"; var qs = new QueryCollection(values); context.Request.Query = qs; + context.RequestServices = services.BuildServiceProvider(); var builder = new ConnectionBuilder(services.BuildServiceProvider()); builder.UseConnectionHandler(); @@ -2311,7 +2620,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } - private static DefaultHttpContext MakeRequest(string path, HttpConnectionContext connection, string format = null) + private static DefaultHttpContext MakeRequest(string path, HttpConnectionContext connection, IServiceCollection serviceCollection, string format = null) { var context = new DefaultHttpContext(); context.Features.Set(new ResponseFeature()); @@ -2327,6 +2636,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests var qs = new QueryCollection(values); context.Request.Query = qs; context.Response.Body = new MemoryStream(); + context.RequestServices = serviceCollection.BuildServiceProvider(); return context; } @@ -2465,6 +2775,35 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } + public class ServiceProviderConnectionHandler : ConnectionHandler + { + public override async Task OnConnectedAsync(ConnectionContext connection) + { + while (true) + { + var result = await connection.Transport.Input.ReadAsync(); + + try + { + if (result.IsCompleted) + { + break; + } + + var context = connection.GetHttpContext(); + var message = context.RequestServices.GetService(); + + // Echo the results + await connection.Transport.Output.WriteAsync(message.Buffer.ToArray()); + } + finally + { + connection.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + } + } + public class ResponseFeature : HttpResponseFeature { public override void OnCompleted(Func callback, object state) @@ -2475,4 +2814,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests { } } + + public class MessageWrapper + { + public ReadOnlySequence Buffer { get; set; } + } } diff --git a/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs b/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs index d603c0d083..7b734ac2fa 100644 --- a/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs +++ b/src/SignalR/common/Http.Connections/test/MapConnectionHandlerTests.cs @@ -13,11 +13,13 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; @@ -305,7 +307,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests [WebSocketsSupportedCondition] public async Task MapConnectionHandlerWithWebSocketSubProtocolSetsProtocol() { - var host = BuildWebHost("/socket", + using var host = BuildWebHost("/socket", options => options.WebSockets.SubProtocolSelector = subprotocols => { Assert.Equal(new[] { "protocol1", "protocol2" }, subprotocols.ToArray()); @@ -314,7 +316,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests await host.StartAsync(); - var feature = host.ServerFeatures.Get(); + var feature = host.Services.GetService().Features.Get(); var address = feature.Addresses.First().Replace("http", "ws") + "/socket"; var client = new ClientWebSocket(); @@ -377,44 +379,52 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests } } - private IWebHost BuildWebHost(Action configure) + private IHost BuildWebHost(Action configure) { - return new WebHostBuilder() - .UseKestrel() - .ConfigureServices(services => + return new HostBuilder() + .ConfigureWebHost(webHostBuilder => { - services.AddConnections(); + webHostBuilder + .UseKestrel() + .ConfigureServices(services => + { + services.AddConnections(); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => configure(endpoints)); + }) + .UseUrls("http://127.0.0.1:0"); }) - .Configure(app => - { - app.UseRouting(); - app.UseEndpoints(endpoints => configure(endpoints)); - }) - .UseUrls("http://127.0.0.1:0") .Build(); } - private IWebHost BuildWebHost(string path, Action configureOptions) where TConnectionHandler : ConnectionHandler + private IHost BuildWebHost(string path, Action configureOptions) where TConnectionHandler : ConnectionHandler { - return new WebHostBuilder() - .UseUrls("http://127.0.0.1:0") - .UseKestrel() - .ConfigureServices(services => + return new HostBuilder() + .ConfigureWebHost(webHostBuilder => { - services.AddConnections(); - }) - .Configure(app => - { - app.UseRouting(); - app.UseEndpoints(routes => + webHostBuilder + .UseUrls("http://127.0.0.1:0") + .UseKestrel() + .ConfigureServices(services => { - routes.MapConnectionHandler(path, configureOptions); + services.AddConnections(); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(routes => + { + routes.MapConnectionHandler(path, configureOptions); + }); + }) + .ConfigureLogging(factory => + { + factory.AddXunit(_output, LogLevel.Trace); }); }) - .ConfigureLogging(factory => - { - factory.AddXunit(_output, LogLevel.Trace); - }) .Build(); } } diff --git a/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs b/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs index 3f90cab019..db83cd073a 100644 --- a/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs +++ b/src/SignalR/common/SignalR.Common/src/Protocol/HandshakeProtocol.cs @@ -26,29 +26,24 @@ namespace Microsoft.AspNetCore.SignalR.Protocol private const string TypePropertyName = "type"; private static JsonEncodedText TypePropertyNameBytes = JsonEncodedText.Encode(TypePropertyName); - private static ConcurrentDictionary> _messageCache = new ConcurrentDictionary>(); + private static readonly ReadOnlyMemory _successHandshakeData; - public static ReadOnlySpan GetSuccessfulHandshake(IHubProtocol protocol) + static HandshakeProtocol() { - ReadOnlyMemory result; - if (!_messageCache.TryGetValue(protocol, out result)) + var memoryBufferWriter = MemoryBufferWriter.Get(); + try { - var memoryBufferWriter = MemoryBufferWriter.Get(); - try - { - WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter); - result = memoryBufferWriter.ToArray(); - _messageCache.TryAdd(protocol, result); - } - finally - { - MemoryBufferWriter.Return(memoryBufferWriter); - } + WriteResponseMessage(HandshakeResponseMessage.Empty, memoryBufferWriter); + _successHandshakeData = memoryBufferWriter.ToArray(); + } + finally + { + MemoryBufferWriter.Return(memoryBufferWriter); } - - return result.Span; } + public static ReadOnlySpan GetSuccessfulHandshake(IHubProtocol protocol) => _successHandshakeData.Span; + /// /// Writes the serialized representation of a to the specified writer. /// diff --git a/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs b/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs index 631220c7de..d50c425528 100644 --- a/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs +++ b/src/SignalR/common/testassets/Tests.Utils/InProcessTestServer.cs @@ -9,16 +9,16 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; namespace Microsoft.AspNetCore.SignalR.Tests { - public abstract class InProcessTestServer : IDisposable + public abstract class InProcessTestServer : IAsyncDisposable { internal abstract event Action ServerLogged; @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public abstract string Url { get; } - public abstract void Dispose(); + public abstract ValueTask DisposeAsync(); } public class InProcessTestServer : InProcessTestServer @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; - private IWebHost _host; + private IHost _host; private IHostApplicationLifetime _lifetime; private readonly IDisposable _logToken; private readonly IDisposable _extraDisposable; @@ -91,15 +91,18 @@ namespace Microsoft.AspNetCore.SignalR.Tests // We're using 127.0.0.1 instead of localhost to ensure that we use IPV4 across different OSes var url = "http://127.0.0.1:0"; - _host = new WebHostBuilder() - .ConfigureLogging(builder => builder + _host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .ConfigureLogging(builder => builder .SetMinimumLevel(LogLevel.Trace) .AddProvider(new ForwardingLoggerProvider(_loggerFactory))) - .UseStartup(typeof(TStartup)) - .UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .Build(); + .UseStartup(typeof(TStartup)) + .UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()); + }).Build(); _logger.LogInformation("Starting test server..."); var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); @@ -116,7 +119,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests _logger.LogInformation("Test Server started"); // Get the URL from the server - _url = _host.ServerFeatures.Get().Addresses.Single(); + _url = _host.Services.GetService().Features.Get().Addresses.Single(); _lifetime = _host.Services.GetRequiredService(); _lifetime.ApplicationStopped.Register(() => @@ -144,12 +147,19 @@ namespace Microsoft.AspNetCore.SignalR.Tests return builder.ToString(); } - public override void Dispose() + public override async ValueTask DisposeAsync() { - _extraDisposable?.Dispose(); - _logger.LogInformation("Shutting down test server"); - _host.Dispose(); - _loggerFactory.Dispose(); + try + { + _extraDisposable?.Dispose(); + _logger.LogInformation("Start shutting down test server"); + } + finally + { + await _host.StopAsync(); + _host.Dispose(); + _loggerFactory.Dispose(); + } } private class ForwardingLoggerProvider : ILoggerProvider diff --git a/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj b/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj index 86cb17cb5f..dc4198b924 100644 --- a/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj +++ b/src/SignalR/common/testassets/Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/docs/specs/HubProtocol.md b/src/SignalR/docs/specs/HubProtocol.md index d8c4ce8938..ed7a1aa832 100644 --- a/src/SignalR/docs/specs/HubProtocol.md +++ b/src/SignalR/docs/specs/HubProtocol.md @@ -543,7 +543,7 @@ MessagePack uses different formats to encode values. Refer to the [MsgPack forma `Invocation` messages have the following structure: ``` -[1, Headers, InvocationId, NonBlocking, Target, [Arguments], [StreamIds]] +[1, Headers, InvocationId, Target, [Arguments], [StreamIds]] ``` * `1` - Message Type - `1` indicates this is an `Invocation` message. diff --git a/src/SignalR/samples/JwtSample/JwtSample.csproj b/src/SignalR/samples/JwtSample/JwtSample.csproj index 552c9d63f2..b8ac2dc1a7 100644 --- a/src/SignalR/samples/JwtSample/JwtSample.csproj +++ b/src/SignalR/samples/JwtSample/JwtSample.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/samples/JwtSample/Program.cs b/src/SignalR/samples/JwtSample/Program.cs index a4bff29a37..06050394b7 100644 --- a/src/SignalR/samples/JwtSample/Program.cs +++ b/src/SignalR/samples/JwtSample/Program.cs @@ -1,29 +1,35 @@ -// 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.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace JwtSample { public class Program { - public static void Main(string[] args) + public static Task Main(string[] args) { - new WebHostBuilder() - .ConfigureLogging(factory => + return Host.CreateDefaultBuilder(args) + .ConfigureWebHost(webHostBuilder => { - factory.AddConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Information); - factory.AddDebug(); + webHostBuilder + .ConfigureLogging(factory => + { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Information); + factory.AddDebug(); + }) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup(); }) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() .Build() - .Run(); + .RunAsync(); } } } diff --git a/src/SignalR/samples/SignalRSamples/Program.cs b/src/SignalR/samples/SignalRSamples/Program.cs index 2f932a18a4..c56a2c5b59 100644 --- a/src/SignalR/samples/SignalRSamples/Program.cs +++ b/src/SignalR/samples/SignalRSamples/Program.cs @@ -3,9 +3,11 @@ using System.IO; using System.Net; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using SignalRSamples.Hubs; @@ -13,36 +15,40 @@ namespace SignalRSamples { public class Program { - public static void Main(string[] args) + public static Task Main(string[] args) { var config = new ConfigurationBuilder() .AddCommandLine(args) .Build(); - var host = new WebHostBuilder() - .UseConfiguration(config) - .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") - .ConfigureLogging(factory => + var host = Host.CreateDefaultBuilder(args) + .ConfigureWebHost(webHostBuilder => { - factory.AddConsole(); - }) - .UseKestrel(options => - { - // Default port - options.ListenLocalhost(5000); - - // Hub bound to TCP end point - options.Listen(IPAddress.Any, 9001, builder => + webHostBuilder + .UseConfiguration(config) + .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") + .ConfigureLogging((c, factory) => { - builder.UseHub(); - }); - }) - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .Build(); + factory.AddConfiguration(c.Configuration.GetSection("Logging")); + factory.AddConsole(); + }) + .UseKestrel(options => + { + // Default port + options.ListenLocalhost(5000); - host.Run(); + // Hub bound to TCP end point + options.Listen(IPAddress.Any, 9001, builder => + { + builder.UseHub(); + }); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup(); + }).Build(); + + return host.RunAsync(); } } } diff --git a/src/SignalR/samples/SocialWeather/Program.cs b/src/SignalR/samples/SocialWeather/Program.cs index 426f89226b..24169976a6 100644 --- a/src/SignalR/samples/SocialWeather/Program.cs +++ b/src/SignalR/samples/SocialWeather/Program.cs @@ -2,29 +2,35 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace SocialWeather { public class Program { - public static void Main(string[] args) + public static Task Main(string[] args) { - var host = new WebHostBuilder() - .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") - .ConfigureLogging(factory => + var host = Host.CreateDefaultBuilder(args) + .ConfigureWebHost(webHostBuilder => { - factory.AddConsole(); - factory.AddFilter("Console", level => level >= LogLevel.Information); + webHostBuilder + .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") + .ConfigureLogging(factory => + { + factory.AddConsole(); + factory.AddFilter("Console", level => level >= LogLevel.Information); + }) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup(); }) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() .Build(); - host.Run(); + return host.RunAsync(); } } } diff --git a/src/SignalR/samples/SocialWeather/SocialWeather.csproj b/src/SignalR/samples/SocialWeather/SocialWeather.csproj index ea9308fee5..fe8e362e2c 100644 --- a/src/SignalR/samples/SocialWeather/SocialWeather.csproj +++ b/src/SignalR/samples/SocialWeather/SocialWeather.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) diff --git a/src/SignalR/server/Core/src/HubConnectionContext.cs b/src/SignalR/server/Core/src/HubConnectionContext.cs index 8611e54bae..01d07d2e8a 100644 --- a/src/SignalR/server/Core/src/HubConnectionContext.cs +++ b/src/SignalR/server/Core/src/HubConnectionContext.cs @@ -37,6 +37,7 @@ namespace Microsoft.AspNetCore.SignalR private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1); private readonly object _receiveMessageTimeoutLock = new object(); private readonly ISystemClock _systemClock; + private readonly CancellationTokenRegistration _closedRegistration; private StreamTracker _streamTracker; private long _lastSendTimeStamp; @@ -66,6 +67,7 @@ namespace Microsoft.AspNetCore.SignalR _connectionContext = connectionContext; _logger = loggerFactory.CreateLogger(); ConnectionAborted = _connectionAbortedTokenSource.Token; + _closedRegistration = connectionContext.ConnectionClosed.Register((state) => ((HubConnectionContext)state).Abort(), this); HubCallerContext = new DefaultHubCallerContext(this); @@ -624,12 +626,6 @@ namespace Microsoft.AspNetCore.SignalR finally { _ = InnerAbortConnection(connection); - - // Use _streamTracker to avoid lazy init from StreamTracker getter if it doesn't exist - if (connection._streamTracker != null) - { - connection._streamTracker.CompleteAll(new OperationCanceledException("The underlying connection was closed.")); - } } static async Task InnerAbortConnection(HubConnectionContext connection) @@ -670,6 +666,17 @@ namespace Microsoft.AspNetCore.SignalR } } + internal void Cleanup() + { + _closedRegistration.Dispose(); + + // Use _streamTracker to avoid lazy init from StreamTracker getter if it doesn't exist + if (_streamTracker != null) + { + _streamTracker.CompleteAll(new OperationCanceledException("The underlying connection was closed.")); + } + } + private static class Log { // Category: HubConnectionContext diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index 7aea15a5d9..403a03e8ae 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -139,6 +139,8 @@ namespace Microsoft.AspNetCore.SignalR } finally { + connectionContext.Cleanup(); + Log.ConnectedEnding(_logger); await _lifetimeManager.OnDisconnectedAsync(connectionContext); } diff --git a/src/SignalR/server/SignalR/test/EndToEndTests.cs b/src/SignalR/server/SignalR/test/EndToEndTests.cs index 7461b2e90e..caebf870c6 100644 --- a/src/SignalR/server/SignalR/test/EndToEndTests.cs +++ b/src/SignalR/server/SignalR/test/EndToEndTests.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [Fact] public async Task CanStartAndStopConnectionUsingDefaultTransport() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var url = server.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorStartingTransport"; } - using (var server = await StartServer(expectedErrorsFilter: ExpectedErrors)) + await using (var server = await StartServer(expectedErrorsFilter: ExpectedErrors)) { var url = server.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [LogLevel(LogLevel.Trace)] public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var url = server.Url + "/echo"; var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url), Transports = transportType, DefaultTransferFormat = TransferFormat.Text }, LoggerFactory); @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTest() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsReceivesAndSendsPartialFramesTest() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task HttpRequestsNotSentWhenWebSocketsTransportRequestedAndSkipNegotiationSet() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); var url = server.Url + "/echo"; @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(HttpTransportType.ServerSentEvents)] public async Task HttpConnectionThrowsIfSkipNegotiationSetAndTransportIsNotWebSockets(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); var url = server.Url + "/echo"; @@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [LogLevel(LogLevel.Trace)] public async Task ConnectionCanSendAndReceiveMessages(HttpTransportType transportType, TransferFormat requestedTransferFormat) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public async Task ConnectionCanSendAndReceiveDifferentMessageSizesWebSocketsTransport(int length) { var message = new string('A', length); - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -379,7 +379,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorStartingTransport"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -433,7 +433,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -469,7 +469,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -539,7 +539,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests private async Task ServerClosesConnectionWithErrorIfHubCannotBeCreated(HttpTransportType transportType) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var logger = LoggerFactory.CreateLogger(); @@ -604,7 +604,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); @@ -643,7 +643,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests writeContext.EventId.Name == "ErrorWithNegotiation"; } - using (var server = await StartServer(ExpectedErrors)) + await using (var server = await StartServer(ExpectedErrors)) { var logger = LoggerFactory.CreateLogger(); diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs index c9e790c11a..3031ee9ffd 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs @@ -221,7 +221,6 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - public async Task StreamingSum(ChannelReader source) { var total = 0; @@ -322,6 +321,14 @@ namespace Microsoft.AspNetCore.SignalR.Tests tcs.TrySetResult(42); } } + + public async Task BlockingMethod() + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Context.ConnectionAborted.Register(state => ((TaskCompletionSource)state).SetResult(null), tcs); + + await tcs.Task; + } } public abstract class TestHub : Hub diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index 962d32bc7c..ceab4322be 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -948,6 +948,30 @@ namespace Microsoft.AspNetCore.SignalR.Tests Assert.True(hasErrorLog); } + [Fact] + public async Task HubMethodListeningToConnectionAbortedClosesOnConnectionContextAbort() + { + using (StartVerifiableLog()) + { + var connectionHandler = HubConnectionHandlerTestUtils.GetHubConnectionHandler(typeof(MethodHub), loggerFactory: LoggerFactory); + + using (var client = new TestClient()) + { + var connectionHandlerTask = await client.ConnectAsync(connectionHandler); + + var invokeTask = client.InvokeAsync(nameof(MethodHub.BlockingMethod)); + + client.Connection.Abort(); + + // If this completes then the server has completed the connection + await connectionHandlerTask.OrTimeout(); + + // Nothing written to connection because it was closed + Assert.False(invokeTask.IsCompleted); + } + } + } + [Fact] public async Task DetailedExceptionEvenWhenNotExplicitlySet() { diff --git a/src/SignalR/server/SignalR/test/MapSignalRTests.cs b/src/SignalR/server/SignalR/test/MapSignalRTests.cs index 8f1a79dc4a..695a5683c7 100644 --- a/src/SignalR/server/SignalR/test/MapSignalRTests.cs +++ b/src/SignalR/server/SignalR/test/MapSignalRTests.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Xunit; namespace Microsoft.AspNetCore.SignalR.Tests @@ -29,9 +30,11 @@ namespace Microsoft.AspNetCore.SignalR.Tests public void NotAddingSignalRServiceThrows() { var executedConfigure = false; - var builder = new WebHostBuilder(); + var builder = new HostBuilder(); - builder + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder .UseKestrel() .ConfigureServices(services => { @@ -54,6 +57,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests "'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.", ex.Message); }) .UseUrls("http://127.0.0.1:0"); + }); using (var host = builder.Build()) { @@ -338,20 +342,24 @@ namespace Microsoft.AspNetCore.SignalR.Tests { } - private IWebHost BuildWebHost(Action configure) + private IHost BuildWebHost(Action configure) { - return new WebHostBuilder() - .UseKestrel() - .ConfigureServices(services => + return new HostBuilder() + .ConfigureWebHost(webHostBuilder => { - services.AddSignalR(); + webHostBuilder + .UseKestrel() + .ConfigureServices(services => + { + services.AddSignalR(); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => configure(endpoints)); + }) + .UseUrls("http://127.0.0.1:0"); }) - .Configure(app => - { - app.UseRouting(); - app.UseEndpoints(endpoints => configure(endpoints)); - }) - .UseUrls("http://127.0.0.1:0") .Build(); } } diff --git a/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs b/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs index 2c059c3edc..6736265a72 100644 --- a/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs +++ b/src/SignalR/server/SignalR/test/WebSocketsTransportTests.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportStopsSendAndReceiveLoopsWhenTransportIsStopped() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportSendsUserAgent() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader"), @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportSendsXRequestedWithHeader() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/httpheader"), @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [WebSocketsSupportedCondition] public async Task WebSocketsTransportStopsWhenConnectionChannelClosed() { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echo"), @@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(TransferFormat.Binary)] public async Task WebSocketsTransportStopsWhenConnectionClosedByTheServer(TransferFormat transferFormat) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); await webSocketsTransport.StartAsync(new Uri(server.WebSocketsUrl + "/echoAndClose"), transferFormat); @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests [InlineData(TransferFormat.Binary)] public async Task WebSocketsTransportSetsTransferFormat(TransferFormat transferFormat) { - using (var server = await StartServer()) + await using (var server = await StartServer()) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: LoggerFactory, accessTokenProvider: null); diff --git a/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs b/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs index ab063ee02a..50ecafbb9f 100644 --- a/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs +++ b/src/SignalR/server/StackExchangeRedis/test/RedisServerFixture.cs @@ -6,12 +6,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; using Xunit; namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests { - public class RedisServerFixture : IAsyncLifetime, IDisposable + public class RedisServerFixture : IAsyncLifetime where TStartup : class { public InProcessTestServer FirstServer { get; private set; } @@ -37,13 +36,24 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests Docker.Default.Start(_logger); } - public Task DisposeAsync() + public async Task DisposeAsync() { - return Task.CompletedTask; + if (Docker.Default != null) + { + await FirstServer.DisposeAsync(); + await SecondServer.DisposeAsync(); + Docker.Default.Stop(_logger); + _logToken.Dispose(); + } } public async Task InitializeAsync() { + if (Docker.Default == null) + { + return; + } + FirstServer = await StartServer(); SecondServer = await StartServer(); } @@ -60,16 +70,5 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests throw; } } - - public void Dispose() - { - if (Docker.Default != null) - { - FirstServer.Dispose(); - SecondServer.Dispose(); - Docker.Default.Stop(_logger); - _logToken.Dispose(); - } - } } } diff --git a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs index 5954f540b5..bef740ad10 100644 --- a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs +++ b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs @@ -19,8 +19,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _app = new AppWithDeps(logger); } - [Fact] - [QuarantinedTest] + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Windows.10.Arm64;Windows.10.Arm64.Open;Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task ChangeFileInDependency() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs index d07f4e25a4..f085a358ae 100644 --- a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs +++ b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs @@ -23,7 +23,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [Fact] - [QuarantinedTest] public async Task RunsWithDotnetWatchEnvVariable() { Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); @@ -36,7 +35,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [Fact] - [QuarantinedTest] public async Task RunsWithIterationEnvVariable() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs index 9383b2d728..e731247241 100644 --- a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs +++ b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs @@ -21,10 +21,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _app = new GlobbingApp(logger); } - [Theory] + [ConditionalTheory] [InlineData(true)] [InlineData(false)] - [QuarantinedTest] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task ChangeCompiledFile(bool usePollingWatcher) { _app.UsePollingWatcher = usePollingWatcher; @@ -42,8 +42,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(2, types); } - [Fact] - [QuarantinedTest] + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task DeleteCompiledFile() { await _app.StartWatcherAsync(); @@ -59,8 +59,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(1, types); } - [Fact] - [QuarantinedTest] + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task DeleteSourceFolder() { await _app.StartWatcherAsync(); @@ -76,8 +76,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests Assert.Equal(1, types); } - [Fact] - [QuarantinedTest] + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task RenameCompiledFile() { await _app.StartWatcherAsync(); @@ -90,7 +90,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [Fact] - [QuarantinedTest] public async Task ChangeExcludedFile() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs b/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs index 4c5907dda6..d3aee43cac 100644 --- a/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs +++ b/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs @@ -77,7 +77,6 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests } [Fact] - [QuarantinedTest] public async Task SingleTfm() { _tempDir diff --git a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs index b7171d5ad2..3dbb26a2e7 100644 --- a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs +++ b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs @@ -23,8 +23,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests _output = logger; } - [Fact] - [QuarantinedTest] + [ConditionalFact] + [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] public async Task RestartProcessOnFileChange() { await _app.StartWatcherAsync(new[] { "--no-exit" }); @@ -43,7 +43,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [Fact] - [QuarantinedTest] public async Task RestartProcessThatTerminatesAfterFileChange() { await _app.StartWatcherAsync(); diff --git a/src/Tools/dotnet-watch/test/ProgramTests.cs b/src/Tools/dotnet-watch/test/ProgramTests.cs index 1b3f33c5b9..8d7557853d 100644 --- a/src/Tools/dotnet-watch/test/ProgramTests.cs +++ b/src/Tools/dotnet-watch/test/ProgramTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests } [Fact] - [QuarantinedTest] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23394")] // Failure on OSX https://dev.azure.com/dnceng/public/_build/results?buildId=706059&view=ms.vss-test-web.build-test-results-tab public async Task ConsoleCancelKey() { _tempDir