Browse Source

Merge pull request #43980 from dotnet-maestro-bot/merge/release/7.0-to-main

[automated] Merge branch 'release/7.0' => 'main'
Bruno Oliveira 3 years ago
parent
commit
31fbf0f14b
39 changed files with 488 additions and 273 deletions
  1. 1 1
      .azure/pipelines/jobs/default-build.yml
  2. 3 0
      eng/Versions.props
  3. 1 1
      eng/common/templates/job/execute-sdl.yml
  4. 1 1
      eng/common/templates/job/onelocbuild.yml
  5. 2 2
      eng/common/templates/job/source-build.yml
  6. 2 2
      eng/common/templates/job/source-index-stage1.yml
  7. 1 1
      eng/common/templates/jobs/jobs.yml
  8. 1 1
      eng/common/templates/jobs/source-build.yml
  9. 4 4
      eng/common/templates/post-build/post-build.yml
  10. 0 2
      eng/scripts/RunHelix.ps1
  11. 6 7
      eng/targets/Helix.Common.props
  12. 4 4
      src/Components/CustomElements/src/Microsoft.AspNetCore.Components.CustomElements.csproj
  13. 1 1
      src/Components/CustomElements/src/js/webpack.config.js
  14. 0 0
      src/Components/Web.JS/dist/Release/blazor.server.js
  15. 7 0
      src/Components/WebAssembly/Authentication.Msal/src/ILLink.Descriptors.xml
  16. 214 131
      src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts
  17. 3 3
      src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json
  18. 17 19
      src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock
  19. 6 0
      src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj
  20. 0 3
      src/Components/test/testassets/BasicTestApp/wwwroot/index.html
  21. 2 1
      src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
  22. 55 8
      src/Http/Http.Extensions/src/RequestDelegateFactory.cs
  23. 42 6
      src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
  24. 1 0
      src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
  25. 3 1
      src/Http/Http.Results/test/AcceptedResultTests.cs
  26. 1 0
      src/Http/Http.Results/test/BadRequestResultTests.cs
  27. 1 0
      src/Http/Http.Results/test/ConflictResultTests.cs
  28. 1 0
      src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
  29. 1 0
      src/Http/Http.Results/test/CreatedResultTests.cs
  30. 1 0
      src/Http/Http.Results/test/NoContentResultTests.cs
  31. 1 0
      src/Http/Http.Results/test/NotFoundResultTests.cs
  32. 1 0
      src/Http/Http.Results/test/OkResultTests.cs
  33. 1 0
      src/Http/Http.Results/test/UnprocessableEntityResultTests.cs
  34. 2 0
      src/OpenApi/src/OpenApiGenerator.cs
  35. 9 1
      src/OpenApi/test/OpenApiRouteHandlerBuilderExtensionTests.cs
  36. 22 2
      src/Servers/Kestrel/Core/src/Internal/TlsConnectionFeature.cs
  37. 1 1
      src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs
  38. 55 52
      src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs
  39. 14 18
      src/Shared/ApiExplorerTypes/ProducesResponseTypeMetadata.cs

+ 1 - 1
.azure/pipelines/jobs/default-build.yml

@@ -106,7 +106,7 @@ jobs:
         vmImage: macOS-11
         vmImage: macOS-11
       ${{ if eq(parameters.agentOs, 'Linux') }}:
       ${{ if eq(parameters.agentOs, 'Linux') }}:
         ${{ if and(eq(parameters.useHostedUbuntu, true), or(ne(variables['System.TeamProject'], 'internal'), in(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule'))) }}:
         ${{ if and(eq(parameters.useHostedUbuntu, true), or(ne(variables['System.TeamProject'], 'internal'), in(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule'))) }}:
-          vmImage: ubuntu-18.04
+          vmImage: ubuntu-20.04
         ${{ if or(eq(parameters.useHostedUbuntu, false), and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule'))) }}:
         ${{ if or(eq(parameters.useHostedUbuntu, false), and(eq(variables['System.TeamProject'], 'internal'), notin(variables['Build.Reason'], 'Manual', 'PullRequest', 'Schedule'))) }}:
           ${{ if eq(variables['System.TeamProject'], 'public') }}:
           ${{ if eq(variables['System.TeamProject'], 'public') }}:
             name: NetCore-Public
             name: NetCore-Public

+ 3 - 0
eng/Versions.props

@@ -159,6 +159,9 @@
     <MicrosoftNETCoreAppRuntimeVersion>$(MicrosoftNETCoreAppRuntimewinx64Version)</MicrosoftNETCoreAppRuntimeVersion>
     <MicrosoftNETCoreAppRuntimeVersion>$(MicrosoftNETCoreAppRuntimewinx64Version)</MicrosoftNETCoreAppRuntimeVersion>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Label="Manual">
   <PropertyGroup Label="Manual">
+    <!-- Bumping the Roslyn version used in order to ingest the new runtime source generators -->
+    <UsingToolMicrosoftNetCompilers>true</UsingToolMicrosoftNetCompilers>
+    <MicrosoftNetCompilersToolsetVersion>4.4.0-3.22452.8</MicrosoftNetCompilersToolsetVersion>
     <!-- DiagnosticAdapter package pinned temporarily until migrated/deprecated -->
     <!-- DiagnosticAdapter package pinned temporarily until migrated/deprecated -->
     <MicrosoftExtensionsDiagnosticAdapterVersion>5.0.0-preview.4.20180.4</MicrosoftExtensionsDiagnosticAdapterVersion>
     <MicrosoftExtensionsDiagnosticAdapterVersion>5.0.0-preview.4.20180.4</MicrosoftExtensionsDiagnosticAdapterVersion>
     <!-- Build tool dependencies -->
     <!-- Build tool dependencies -->

+ 1 - 1
eng/common/templates/job/execute-sdl.yml

@@ -53,7 +53,7 @@ jobs:
       demands: Cmd
       demands: Cmd
     # If it's not devdiv, it's dnceng
     # If it's not devdiv, it's dnceng
     ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
     ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
-      name: NetCore1ESPool-Internal
+      name: NetCore1ESPool-Svc-Internal
       demands: ImageOverride -equals windows.vs2019.amd64
       demands: ImageOverride -equals windows.vs2019.amd64
   steps:
   steps:
   - checkout: self
   - checkout: self

+ 1 - 1
eng/common/templates/job/onelocbuild.yml

@@ -40,7 +40,7 @@ jobs:
         demands: Cmd
         demands: Cmd
       # If it's not devdiv, it's dnceng
       # If it's not devdiv, it's dnceng
       ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
       ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
-        name: NetCore1ESPool-Internal
+        name: NetCore1ESPool-Svc-Internal
         demands: ImageOverride -equals windows.vs2019.amd64
         demands: ImageOverride -equals windows.vs2019.amd64
 
 
   variables:
   variables:

+ 2 - 2
eng/common/templates/job/source-build.yml

@@ -46,10 +46,10 @@ jobs:
     # source-build builds run in Docker, including the default managed platform.
     # source-build builds run in Docker, including the default managed platform.
     pool:
     pool:
       ${{ if eq(variables['System.TeamProject'], 'public') }}:
       ${{ if eq(variables['System.TeamProject'], 'public') }}:
-        name: NetCore-Public
+        name: NetCore-Svc-Public
         demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open
         demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open
       ${{ if eq(variables['System.TeamProject'], 'internal') }}:
       ${{ if eq(variables['System.TeamProject'], 'internal') }}:
-        name: NetCore1ESPool-Internal
+        name: NetCore1ESPool-Svc-Internal
         demands: ImageOverride -equals Build.Ubuntu.1804.Amd64
         demands: ImageOverride -equals Build.Ubuntu.1804.Amd64
   ${{ if ne(parameters.platform.pool, '') }}:
   ${{ if ne(parameters.platform.pool, '') }}:
     pool: ${{ parameters.platform.pool }}
     pool: ${{ parameters.platform.pool }}

+ 2 - 2
eng/common/templates/job/source-index-stage1.yml

@@ -28,10 +28,10 @@ jobs:
   ${{ if eq(parameters.pool, '') }}:
   ${{ if eq(parameters.pool, '') }}:
     pool:
     pool:
       ${{ if eq(variables['System.TeamProject'], 'public') }}:
       ${{ if eq(variables['System.TeamProject'], 'public') }}:
-        name: NetCore-Public
+        name: NetCore-Svc-Public
         demands: ImageOverride -equals windows.vs2019.amd64.open
         demands: ImageOverride -equals windows.vs2019.amd64.open
       ${{ if eq(variables['System.TeamProject'], 'internal') }}:
       ${{ if eq(variables['System.TeamProject'], 'internal') }}:
-        name: NetCore1ESPool-Internal
+        name: NetCore1ESPool-Svc-Internal
         demands: ImageOverride -equals windows.vs2019.amd64
         demands: ImageOverride -equals windows.vs2019.amd64
 
 
   steps:
   steps:

+ 1 - 1
eng/common/templates/jobs/jobs.yml

@@ -95,7 +95,7 @@ jobs:
             demands: Cmd
             demands: Cmd
           # If it's not devdiv, it's dnceng
           # If it's not devdiv, it's dnceng
           ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
           ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}:
-            name: NetCore1ESPool-Internal
+            name: NetCore1ESPool-Svc-Internal
             demands: ImageOverride -equals windows.vs2019.amd64
             demands: ImageOverride -equals windows.vs2019.amd64
 
 
         runAsPublic: ${{ parameters.runAsPublic }}
         runAsPublic: ${{ parameters.runAsPublic }}

+ 1 - 1
eng/common/templates/jobs/source-build.yml

@@ -14,7 +14,7 @@ parameters:
   # This is the default platform provided by Arcade, intended for use by a managed-only repo.
   # This is the default platform provided by Arcade, intended for use by a managed-only repo.
   defaultManagedPlatform:
   defaultManagedPlatform:
     name: 'Managed'
     name: 'Managed'
-    container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-3e800f1-20190501005343'
+    container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8-20220809204800-17a4aab'
 
 
   # Defines the platforms on which to run build jobs. One job is created for each platform, and the
   # Defines the platforms on which to run build jobs. One job is created for each platform, and the
   # object in this array is sent to the job template as 'platform'. If no platforms are specified,
   # object in this array is sent to the job template as 'platform'. If no platforms are specified,

+ 4 - 4
eng/common/templates/post-build/post-build.yml

@@ -106,7 +106,7 @@ stages:
           demands: Cmd
           demands: Cmd
         # If it's not devdiv, it's dnceng
         # If it's not devdiv, it's dnceng
         ${{ else }}:
         ${{ else }}:
-          name: NetCore1ESPool-Internal
+          name: NetCore1ESPool-Svc-Internal
           demands: ImageOverride -equals windows.vs2019.amd64
           demands: ImageOverride -equals windows.vs2019.amd64
 
 
       steps:
       steps:
@@ -143,7 +143,7 @@ stages:
           demands: Cmd
           demands: Cmd
         # If it's not devdiv, it's dnceng
         # If it's not devdiv, it's dnceng
         ${{ else }}:
         ${{ else }}:
-          name: NetCore1ESPool-Internal
+          name: NetCore1ESPool-Svc-Internal
           demands: ImageOverride -equals windows.vs2019.amd64
           demands: ImageOverride -equals windows.vs2019.amd64
       steps:
       steps:
         - template: setup-maestro-vars.yml
         - template: setup-maestro-vars.yml
@@ -203,7 +203,7 @@ stages:
           demands: Cmd
           demands: Cmd
         # If it's not devdiv, it's dnceng
         # If it's not devdiv, it's dnceng
         ${{ else }}:
         ${{ else }}:
-          name: NetCore1ESPool-Internal
+          name: NetCore1ESPool-Svc-Internal
           demands: ImageOverride -equals windows.vs2019.amd64
           demands: ImageOverride -equals windows.vs2019.amd64
       steps:
       steps:
         - template: setup-maestro-vars.yml
         - template: setup-maestro-vars.yml
@@ -262,7 +262,7 @@ stages:
           demands: Cmd
           demands: Cmd
         # If it's not devdiv, it's dnceng
         # If it's not devdiv, it's dnceng
         ${{ else }}:
         ${{ else }}:
-          name: NetCore1ESPool-Internal
+          name: NetCore1ESPool-Svc-Internal
           demands: ImageOverride -equals windows.vs2019.amd64
           demands: ImageOverride -equals windows.vs2019.amd64
       steps:
       steps:
         - template: setup-maestro-vars.yml
         - template: setup-maestro-vars.yml

+ 0 - 2
eng/scripts/RunHelix.ps1

@@ -11,9 +11,7 @@
     Debian.11.Amd64.Open
     Debian.11.Amd64.Open
     Mariner
     Mariner
     Redhat.7.Amd64.Open
     Redhat.7.Amd64.Open
-    Ubuntu.1804.Amd64.Open
     Ubuntu.2004.Amd64.Open
     Ubuntu.2004.Amd64.Open
-    OSX.1015.Amd64.Open
     OSX.1100.Amd64.Open
     OSX.1100.Amd64.Open
     Windows.10.Amd64.Server20H2.Open
     Windows.10.Amd64.Server20H2.Open
     Windows.11.Amd64.Client.Open
     Windows.11.Amd64.Client.Open

+ 6 - 7
eng/targets/Helix.Common.props

@@ -1,11 +1,11 @@
 <Project>
 <Project>
   <!-- This file is shared between Helix.proj and .csproj files. -->
   <!-- This file is shared between Helix.proj and .csproj files. -->
   <PropertyGroup>
   <PropertyGroup>
-    <HelixQueueAlpine314>(Alpine.314.Amd64.Open)Ubuntu.18[email protected]/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64-20210910135833-1848e19</HelixQueueAlpine314>
-    <HelixQueueDebian11>(Debian.11.Amd64.Open)Ubuntu.18[email protected]/dotnet-buildtools/prereqs:debian-11-helix-amd64-20211001171307-0ece9b3</HelixQueueDebian11>
-    <HelixQueueFedora34>(Fedora.34.Amd64.Open)Ubuntu.18[email protected]/dotnet-buildtools/prereqs:fedora-34-helix-20210924174119-4f64125</HelixQueueFedora34>
-    <HelixQueueMariner>(Mariner)Ubuntu.18[email protected]/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix-20210528192219-92bf620</HelixQueueMariner>
-    <HelixQueueArmDebian11>(Debian.11.Arm64.Open)Ubuntu.18[email protected]/dotnet-buildtools/prereqs:debian-11-helix-arm64v8-20211001171229-97d8652</HelixQueueArmDebian11>
+    <HelixQueueAlpine314>(Alpine.314.Amd64.Open)Ubuntu.20[email protected]/dotnet-buildtools/prereqs:alpine-3.14-helix-amd64-20210910135833-1848e19</HelixQueueAlpine314>
+    <HelixQueueDebian11>(Debian.11.Amd64.Open)Ubuntu.20[email protected]/dotnet-buildtools/prereqs:debian-11-helix-amd64-20211001171307-0ece9b3</HelixQueueDebian11>
+    <HelixQueueFedora34>(Fedora.34.Amd64.Open)Ubuntu.20[email protected]/dotnet-buildtools/prereqs:fedora-34-helix-20210924174119-4f64125</HelixQueueFedora34>
+    <HelixQueueMariner>(Mariner)Ubuntu.20[email protected]/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix-20210528192219-92bf620</HelixQueueMariner>
+    <HelixQueueArmDebian11>(Debian.11.Arm64.Open)Ubuntu.20[email protected]/dotnet-buildtools/prereqs:debian-11-helix-arm64v8-20211001171229-97d8652</HelixQueueArmDebian11>
 
 
     <!-- Do not attempt to override global property. -->
     <!-- Do not attempt to override global property. -->
     <RunQuarantinedTests Condition=" '$(RunQuarantinedTests)' == '' ">false</RunQuarantinedTests>
     <RunQuarantinedTests Condition=" '$(RunQuarantinedTests)' == '' ">false</RunQuarantinedTests>
@@ -27,7 +27,7 @@
       <!-- aspnetcore-ci[-official] (ci.yml) -->
       <!-- aspnetcore-ci[-official] (ci.yml) -->
       <!-- aspnetcore-quarantined-pr (quarantined-pr.yml) -->
       <!-- aspnetcore-quarantined-pr (quarantined-pr.yml) -->
       <ItemGroup>
       <ItemGroup>
-        <HelixAvailableTargetQueue Include="Ubuntu.1804.Amd64.Open" Platform="Linux" />
+        <HelixAvailableTargetQueue Include="Ubuntu.2004.Amd64.Open" Platform="Linux" />
         <HelixAvailableTargetQueue Include="OSX.1100.Amd64.Open" Platform="OSX" />
         <HelixAvailableTargetQueue Include="OSX.1100.Amd64.Open" Platform="OSX" />
         <HelixAvailableTargetQueue Include="Windows.11.Amd64.Client.Open" Platform="Windows" />
         <HelixAvailableTargetQueue Include="Windows.11.Amd64.Client.Open" Platform="Windows" />
       </ItemGroup>
       </ItemGroup>
@@ -38,7 +38,6 @@
       <ItemGroup>
       <ItemGroup>
         <!-- Linux -->
         <!-- Linux -->
         <HelixAvailableTargetQueue Include="Redhat.7.Amd64.Open" Platform="Linux" />
         <HelixAvailableTargetQueue Include="Redhat.7.Amd64.Open" Platform="Linux" />
-        <HelixAvailableTargetQueue Include="Ubuntu.2004.Amd64.Open" Platform="Linux" />
 
 
         <!-- Containers -->
         <!-- Containers -->
         <HelixAvailableTargetQueue Include="$(HelixQueueAlpine314)" Platform="Linux" />
         <HelixAvailableTargetQueue Include="$(HelixQueueAlpine314)" Platform="Linux" />

+ 4 - 4
src/Components/CustomElements/src/Microsoft.AspNetCore.Components.CustomElements.csproj

@@ -23,7 +23,7 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <YarnInputs Include="$(YarnWorkingDir)**" Exclude="$(YarnWorkingDir)node_modules\**;$(YarnWorkingDir)*.d.ts;$(YarnWorkingDir)dist\**" />
     <YarnInputs Include="$(YarnWorkingDir)**" Exclude="$(YarnWorkingDir)node_modules\**;$(YarnWorkingDir)*.d.ts;$(YarnWorkingDir)dist\**" />
-    <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\BlazorCustomElements.js" />
+    <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\Microsoft.AspNetCore.Components.CustomElements.lib.module.js" />
 
 
     <Content Remove="$(YarnWorkingDir)**" />
     <Content Remove="$(YarnWorkingDir)**" />
     <None Include="$(YarnWorkingDir)*" Exclude="$(YarnWorkingDir)node_modules\**" />
     <None Include="$(YarnWorkingDir)*" Exclude="$(YarnWorkingDir)node_modules\**" />
@@ -47,7 +47,7 @@
     <ItemGroup>
     <ItemGroup>
       <FileWrites Include="$(JsCompilationCacheFile)" />
       <FileWrites Include="$(JsCompilationCacheFile)" />
     </ItemGroup>
     </ItemGroup>
-    
+
   </Target>
   </Target>
 
 
   <Target Name="CompileJs" Condition="'$(BuildNodeJS)' != 'false' AND '$(DesignTimeBuild)' != 'true'" Inputs="$(JsCompilationCacheFile)" Outputs="@(YarnOutputs)">
   <Target Name="CompileJs" Condition="'$(BuildNodeJS)' != 'false' AND '$(DesignTimeBuild)' != 'true'" Inputs="$(JsCompilationCacheFile)" Outputs="@(YarnOutputs)">
@@ -59,13 +59,13 @@
 
 
   </Target>
   </Target>
 
 
-  <Target Name="IncludeCompileJsOutput">
+  <Target Name="IncludeCompileJsOutput" BeforeTargets="_ResolveJsModuleInputs" DependsOnTargets="CompileJs">
     <ItemGroup>
     <ItemGroup>
       <_JsBuildOutput Include="$(YarnWorkingDir)dist\$(Configuration)\**" Exclude="$(YarnWorkingDir)dist\.gitignore" />
       <_JsBuildOutput Include="$(YarnWorkingDir)dist\$(Configuration)\**" Exclude="$(YarnWorkingDir)dist\.gitignore" />
     </ItemGroup>
     </ItemGroup>
 
 
     <DefineStaticWebAssets Condition="'@(_JsBuildOutput)' != ''"
     <DefineStaticWebAssets Condition="'@(_JsBuildOutput)' != ''"
-      SourceType="Computed"
+      SourceType="Discovered"
       SourceId="$(PackageId)"
       SourceId="$(PackageId)"
       ContentRoot="$(YarnWorkingDir)dist\$(Configuration)\"
       ContentRoot="$(YarnWorkingDir)dist\$(Configuration)\"
       BasePath="_content\$(PackageId)"
       BasePath="_content\$(PackageId)"

+ 1 - 1
src/Components/CustomElements/src/js/webpack.config.js

@@ -12,7 +12,7 @@ module.exports = (env, args) => ({
         rules: [{ test: /\.ts?$/, loader: 'ts-loader' }]
         rules: [{ test: /\.ts?$/, loader: 'ts-loader' }]
     },
     },
     entry: {
     entry: {
-        'BlazorCustomElements': './BlazorCustomElements.ts',
+        'Microsoft.AspNetCore.Components.CustomElements.lib.module': './BlazorCustomElements.ts',
     },
     },
     output: { path: path.join(__dirname, 'dist', args.mode == 'development' ? 'Debug' : 'Release'), filename: '[name].js' },
     output: { path: path.join(__dirname, 'dist', args.mode == 'development' ? 'Debug' : 'Release'), filename: '[name].js' },
     performance: {
     performance: {

File diff suppressed because it is too large
+ 0 - 0
src/Components/Web.JS/dist/Release/blazor.server.js


+ 7 - 0
src/Components/WebAssembly/Authentication.Msal/src/ILLink.Descriptors.xml

@@ -0,0 +1,7 @@
+<linker>
+  <assembly fullname="Microsoft.Authentication.WebAssembly.Msal">
+    <type fullname="Microsoft.Authentication.WebAssembly.Msal.Models.MsalProviderOptions" preserve="all" />
+    <type fullname="Microsoft.Authentication.WebAssembly.Msal.Models.MsalCacheOptions" preserve="all" />
+    <type fullname="Microsoft.Authentication.WebAssembly.Msal.MsalAuthenticationOptions" preserve="all" />
+  </assembly>
+</linker>

+ 214 - 131
src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts

@@ -43,6 +43,54 @@ interface AuthorizeService {
     completeSignOut(url: string): Promise<AuthenticationResult>;
     completeSignOut(url: string): Promise<AuthenticationResult>;
 }
 }
 
 
+// These are the values for the .NET logger LogLevel. 
+// We only use debug and trace
+export enum LogLevel {
+    Trace = 0,
+    Debug = 1
+}
+
+interface JavaScriptLoggingOptions {
+    debugEnabled: boolean;
+    traceEnabled: boolean;
+}
+
+export class Logger {
+    public debug: boolean;
+    public trace: boolean;
+    public constructor(options: JavaScriptLoggingOptions) {
+        this.debug = options.debugEnabled;
+        this.trace = options.traceEnabled;
+    }
+
+    log(level: LogLevel, message: string): void {
+        if ((level == LogLevel.Trace && this.trace) ||
+            (level == LogLevel.Debug && this.debug)) {
+            const levelString = level == LogLevel.Trace ? 'trce' : 'dbug';
+            console.debug(
+                // Logs in the following format to keep consistency with the way ASP.NET Core logs to the console while avoiding the
+                // additional overhead of passing the logger as a JSObjectReference
+                // dbug: Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationService[0]
+                //       <<message>>         
+                // trce: Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationService[0]
+                //       <<message>>
+                `${levelString}: Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationService[0]
+      ${message}`);
+        }
+    }
+}
+
+export interface AuthenticationContext {
+    state?: unknown;
+    interactiveRequest: InteractiveAuthenticationRequest;
+}
+
+export interface InteractiveAuthenticationRequest {
+    interaction: string;
+    scopes?: string[];
+    additionalRequestParameters?: { [key: string]: any };
+};
+
 interface AuthorizeServiceConfiguration extends Msal.Configuration {
 interface AuthorizeServiceConfiguration extends Msal.Configuration {
     defaultAccessTokenScopes: string[];
     defaultAccessTokenScopes: string[];
     additionalScopesToConsent: string[];
     additionalScopesToConsent: string[];
@@ -51,14 +99,48 @@ interface AuthorizeServiceConfiguration extends Msal.Configuration {
 
 
 class MsalAuthorizeService implements AuthorizeService {
 class MsalAuthorizeService implements AuthorizeService {
     private readonly _msalApplication: Msal.PublicClientApplication;
     private readonly _msalApplication: Msal.PublicClientApplication;
-    private _account: Msal.AccountInfo | undefined | null;
+    private _account: Msal.AccountInfo | null | undefined;
     private _redirectCallback: Promise<AuthenticationResult | null> | undefined;
     private _redirectCallback: Promise<AuthenticationResult | null> | undefined;
-    private _requestedScopes: string[] | undefined;
 
 
-    constructor(private readonly _settings: AuthorizeServiceConfiguration) {
+    constructor(private readonly _settings: AuthorizeServiceConfiguration, private readonly _logger: Logger) {
         if (this._settings.auth?.knownAuthorities?.length == 0) {
         if (this._settings.auth?.knownAuthorities?.length == 0) {
             this._settings.auth.knownAuthorities = [new URL(this._settings.auth.authority!).hostname]
             this._settings.auth.knownAuthorities = [new URL(this._settings.auth.authority!).hostname]
         }
         }
+
+        this._settings.system = this._settings.system || {};
+
+        this._settings.system.navigationClient = {
+            async navigateInternal(url: string, options: Msal.NavigationOptions): Promise<boolean> {
+                // We always replace the URL
+                _logger.log(LogLevel.Trace, `Navigating to ${url}`);
+                location.replace(url);
+                return false;
+            },
+            async navigateExternal(url: string, options: Msal.NavigationOptions): Promise<boolean> {
+                // We always replace the URL
+                _logger.log(LogLevel.Trace, `Navigating to ${url}`);
+                location.replace(url);
+                return false;
+            }
+        }
+
+        this._settings.system.loggerOptions = {
+            logLevel: _logger.trace ? Msal.LogLevel.Trace : (_logger.debug ? Msal.LogLevel.Verbose : Msal.LogLevel.Warning),
+            loggerCallback: (level, message, containsPii) => {
+                if (containsPii) {
+                    return;
+                }
+                if (level === Msal.LogLevel.Trace) {
+                    _logger.log(LogLevel.Trace, message);
+                    return;
+                }
+
+                // We only have Debug/Trace, so anything above, we only log when Debug is enabled.
+                _logger.log(LogLevel.Debug, message);
+                return;
+            }
+        };
+
         this._msalApplication = new Msal.PublicClientApplication(this._settings);
         this._msalApplication = new Msal.PublicClientApplication(this._settings);
     }
     }
 
 
@@ -81,35 +163,12 @@ class MsalAuthorizeService implements AuthorizeService {
             return;
             return;
         }
         }
 
 
-        const scopes: string[] = [];
-        if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) {
-            scopes.push(...this._settings.defaultAccessTokenScopes)
-        }
-
-        if (this._settings.additionalScopesToConsent && this._settings.additionalScopesToConsent.length > 0) {
-            scopes.push(...this._settings.additionalScopesToConsent);
-        }
-
-        if (this._requestedScopes && this._requestedScopes.length > 0) {
-            scopes.push(...this._requestedScopes);
-        }
-
-        const silentRequest = {
-            redirectUri: this._settings.auth?.redirectUri,
-            account: account,
-            scopes: scopes
-        };
-
-        try {
-            const response = await this._msalApplication.acquireTokenSilent(silentRequest);
-            return response.idTokenClaims;
-        } catch (e) {
-            await this.signInCore(silentRequest);
-        }
+        return account.idTokenClaims;
     }
     }
 
 
     async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
     async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
         try {
         try {
+            this.trace('getAccessToken', request);
             const newToken = await this.getTokenCore(request?.scopes);
             const newToken = await this.getTokenCore(request?.scopes);
             return {
             return {
                 status: AccessTokenResultStatus.Success,
                 status: AccessTokenResultStatus.Success,
@@ -128,72 +187,76 @@ class MsalAuthorizeService implements AuthorizeService {
             throw new Error('Failed to retrieve token, no account found.');
             throw new Error('Failed to retrieve token, no account found.');
         }
         }
 
 
-        this._requestedScopes = scopes;
         const silentRequest = {
         const silentRequest = {
             redirectUri: this._settings.auth?.redirectUri,
             redirectUri: this._settings.auth?.redirectUri,
             account: account,
             account: account,
             scopes: scopes || this._settings.defaultAccessTokenScopes
             scopes: scopes || this._settings.defaultAccessTokenScopes
         };
         };
 
 
+        this.debug(`Provisioning a token silently for scopes '${silentRequest.scopes}'`)
+        this.trace('_msalApplication.acquireTokenSilent', silentRequest);
         const response = await this._msalApplication.acquireTokenSilent(silentRequest);
         const response = await this._msalApplication.acquireTokenSilent(silentRequest);
-        return {
+        this.trace('_msalApplication.acquireTokenSilent-response', response);
+
+        if (response.scopes.length === 0 || response.accessToken === '') {
+            throw new Error('Scopes not granted.');
+        }
+
+        const result = {
             value: response.accessToken,
             value: response.accessToken,
             grantedScopes: response.scopes,
             grantedScopes: response.scopes,
             expires: response.expiresOn
             expires: response.expiresOn
         };
         };
+
+        this.trace('getAccessToken-result', result);
+
+        return result;
     }
     }
 
 
-    async signIn(state: any) {
+    async signIn(context: AuthenticationContext) {
+        this.trace('signIn', context);
         try {
         try {
             // Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
             // Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
             this.purgeState();
             this.purgeState();
 
 
-            const request: Partial<Msal.AuthorizationUrlRequest> = {
-                redirectUri: this._settings.auth.redirectUri!,
-                state: await this.saveState(state)
-            };
-
-            if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) {
-                request.scopes = this._settings.defaultAccessTokenScopes;
-            }
-
-            if (this._settings.additionalScopesToConsent && this._settings.additionalScopesToConsent.length > 0) {
-                request.extraScopesToConsent = this._settings.additionalScopesToConsent;
-            }
-
-            const result = await this.signInCore(request);
-            if (!result) {
-                return this.redirect();
-            } else if (this.isMsalError(result)) {
-                return this.error(result.errorMessage);
-            }
-
-            try {
-                if (this._settings.defaultAccessTokenScopes?.length > 0) {
-                    // This provisions the token as part of the sign-in flow eagerly so that is already in the cache
-                    // when the app asks for it.
-                    const account = this.getAccount();
-                    if (!account) {
-                        return this.error("No account to get tokens for.");
-                    }
-                    const silentRequest : Msal.SilentRequest = {
-                        redirectUri: request.redirectUri,
-                        account: account,
-                        scopes: request?.scopes?.concat(request.extraScopesToConsent || []) || []
-                    };
-                    await this._msalApplication.acquireTokenSilent(silentRequest);
+            const { state, interactiveRequest } = context;
+
+            if (interactiveRequest && interactiveRequest.interaction === 'GetToken') {
+                this.debug('Acquiring additional token.');
+                const request: Msal.RedirectRequest = {
+                    scopes: interactiveRequest.scopes || [],
+                    state: this.saveState(context.state),
+                    ...interactiveRequest.additionalRequestParameters
+                };
+                this.trace('getInteractiveToken-Request', request);
+                await this._msalApplication.acquireTokenRedirect(request);
+                return this.success(state);
+            } else {
+                const request: Partial<Msal.AuthorizationUrlRequest> = {
+                    redirectUri: this._settings.auth.redirectUri!,
+                    state: this.saveState(context.state),
+                    ...interactiveRequest?.additionalRequestParameters
+                };
+
+                request.scopes = request.scopes || this._settings.defaultAccessTokenScopes || [];
+
+                const result = await this.signInCore(request);
+                this.trace('signIn-Response', result);
+                if (!result) {
+                    return this.redirect();
+                } else if (this.isMsalError(result)) {
+                    return this.error(result.errorMessage);
                 }
                 }
-            } catch (e) {
-                return this.error((e as Msal.AuthError).errorMessage);
-            }
 
 
-            return this.success(state);
+                return this.success(state);
+            }
         } catch (e) {
         } catch (e) {
             return this.error((e as Error).message);
             return this.error((e as Error).message);
         }
         }
     }
     }
 
 
     async signInCore(request: Partial<Msal.AuthorizationUrlRequest>): Promise<Msal.AuthenticationResult | Msal.AuthError | undefined> {
     async signInCore(request: Partial<Msal.AuthorizationUrlRequest>): Promise<Msal.AuthenticationResult | Msal.AuthError | undefined> {
+        this.trace('signIn-Request', request);
         const loginMode = this._settings.loginMode.toLowerCase();
         const loginMode = this._settings.loginMode.toLowerCase();
         if (loginMode === 'redirect') {
         if (loginMode === 'redirect') {
             return this.signInWithRedirect(request as Msal.RedirectRequest);
             return this.signInWithRedirect(request as Msal.RedirectRequest);
@@ -204,20 +267,25 @@ class MsalAuthorizeService implements AuthorizeService {
 
 
     private async signInWithRedirect(request: Msal.RedirectRequest) {
     private async signInWithRedirect(request: Msal.RedirectRequest) {
         try {
         try {
+            this.debug('Starting sign-in redirect.');
             return await this._msalApplication.loginRedirect(request);
             return await this._msalApplication.loginRedirect(request);
         } catch (e) {
         } catch (e) {
+            this.debug(`Sign-in redirect failed: '${(e as Error).message}'.`);
             return e as any;
             return e as any;
         }
         }
     }
     }
 
 
     private async signInWithPopup(request: Msal.PopupRequest) {
     private async signInWithPopup(request: Msal.PopupRequest) {
         try {
         try {
+            this.debug('Starting sign-in pop-up');
             return await this._msalApplication.loginPopup(request);
             return await this._msalApplication.loginPopup(request);
         } catch (e) {
         } catch (e) {
             // If the user explicitly cancelled the pop-up, avoid performing a redirect.
             // If the user explicitly cancelled the pop-up, avoid performing a redirect.
             if (this.isMsalError(e) && e.errorCode !== Msal.BrowserAuthErrorMessage.userCancelledError.code) {
             if (this.isMsalError(e) && e.errorCode !== Msal.BrowserAuthErrorMessage.userCancelledError.code) {
+                this.debug('User canceled sign-in pop-up');
                 this.signInWithRedirect(request);
                 this.signInWithRedirect(request);
             } else {
             } else {
+                this.debug(`Sign-in pop-up failed: '${(e as Error).message}'.`);
                 return e as any;
                 return e as any;
             }
             }
         }
         }
@@ -226,64 +294,71 @@ class MsalAuthorizeService implements AuthorizeService {
     async completeSignIn() {
     async completeSignIn() {
         // Make sure that the redirect handler has completed execution before
         // Make sure that the redirect handler has completed execution before
         // completing sign in.
         // completing sign in.
-        var authenticationResult = await this._redirectCallback;
-        if (authenticationResult) {
-            return authenticationResult;
+        try {
+            this.debug('Completing sign-in redirect.');
+            var authenticationResult = await this._redirectCallback;
+            this.trace('completeSignIn-result', authenticationResult);
+            if (authenticationResult) {
+                this.trace('completeSignIn-success', authenticationResult);
+                return authenticationResult;
+            }
+            this.debug('No authentication result.');
+            return this.operationCompleted();
+        } catch (e) {
+            this.debug(`completeSignIn-error:'${(e as Error).message}'`);
+            return this.error((e as Error).message);
         }
         }
-        return this.operationCompleted();
     }
     }
 
 
-    async signOut(state: any) {
-        // We are about to sign out, so clear any state before we do so and leave just the sign out state for
-        // the current sign out flow.
-        this.purgeState();
+    async signOut(context: AuthenticationContext) {
+        this.trace('signOut', context);
+        try {
+            // Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
+            this.purgeState();
 
 
-        const logoutStateId = await this.saveState(state);
+            const { state, interactiveRequest } = context;
 
 
-        // msal.js doesn't support providing logout state, so we shim it by putting the identifier in session storage
-        // and using that on the logout callback to workout the problems.
-        sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.LogoutState`, logoutStateId);
+            const request: Partial<Msal.EndSessionRequest> = {
+                postLogoutRedirectUri: this._settings.auth.postLogoutRedirectUri,
+                state: this.saveState(state),
+                ...interactiveRequest?.additionalRequestParameters
+            };
+            this.trace('signOut-Request', request);
 
 
-        this._msalApplication.logout();
+            await this._msalApplication.logoutRedirect(request);
 
 
-        // We are about to be redirected.
-        return this.redirect();
+            // We are about to be redirected.
+            return this.redirect();
+        } catch (e) {
+            return this.error((e as Error).message);
+        }
     }
     }
 
 
     async completeSignOut(url: string) {
     async completeSignOut(url: string) {
-        const logoutStateId = sessionStorage.getItem(`${AuthenticationService._infrastructureKey}.LogoutState`);
-        const updatedUrl = new URL(url);
-        updatedUrl.search = `?state=${logoutStateId}`;
-        const logoutState = await this.retrieveState(updatedUrl.href, null, /*isLogout*/ true);
-
-        sessionStorage.removeItem(`${AuthenticationService._infrastructureKey}.LogoutState`);
-
-        if (logoutState) {
-            return this.success(logoutState);
-        } else {
+        this.trace('completeSignOut-request', url);
+        try {
+            this.debug('Completing sign-out redirect.');
+            var authenticationResult = await this._redirectCallback;
+            this.trace('completeSignOut-result', authenticationResult);
+            if (authenticationResult) {
+                this.trace('completeSignOut-success', authenticationResult);
+                return authenticationResult;
+            }
+            this.debug('No authentication result.');
             return this.operationCompleted();
             return this.operationCompleted();
+        } catch (e) {
+            this.debug(`completeSignOut-error:'${(e as Error).message}'`);
+            return this.error((e as Error).message);
         }
         }
     }
     }
 
 
     // msal.js only allows a string as the account state and it simply attaches it to the sign-in request state.
     // msal.js only allows a string as the account state and it simply attaches it to the sign-in request state.
     // Given that we don't want to serialize the entire state and put it in the query string, we need to serialize the
     // Given that we don't want to serialize the entire state and put it in the query string, we need to serialize the
     // state ourselves and pass an identifier to retrieve it while in the callback flow.
     // state ourselves and pass an identifier to retrieve it while in the callback flow.
-    async saveState<T>(state: T): Promise<string> {
-        const base64UrlIdentifier = await new Promise<string>((resolve, reject) => {
-            const reader = new FileReader();
-            reader.onloadend = evt => resolve((evt?.target?.result as string)
-                // The result comes back as a base64 string inside a dataUrl.
-                // We remove the prefix and convert it to base64url by replacing '+' with '-', '/' with '_' and removing '='.
-                .split(',')[1].replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''));
-            reader.onerror = evt => reject(evt.target?.error?.message);
-
-            // We generate a base 64 url encoded string of random data.
-            const entropy = window.crypto.getRandomValues(new Uint8Array(32));
-            reader.readAsDataURL(new Blob([entropy]));
-        });
-
-        sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.AuthorizeService.${base64UrlIdentifier}`, JSON.stringify(state));
-        return base64UrlIdentifier;
+    saveState<T>(state: T): string {
+        const identifier = window.crypto.randomUUID();
+        sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.AuthorizeService.${identifier}`, JSON.stringify(state));
+        return identifier;
     }
     }
 
 
     retrieveState<T>(url: string | null, providedState: string | null = null, isLogout: boolean = false): T | undefined {
     retrieveState<T>(url: string | null, providedState: string | null = null, isLogout: boolean = false): T | undefined {
@@ -321,37 +396,36 @@ class MsalAuthorizeService implements AuthorizeService {
         }
         }
     }
     }
 
 
-    async initializeMsalHandler() {
-        this._redirectCallback = this._msalApplication.handleRedirectPromise().then(
-            (result: Msal.AuthenticationResult | null) => this.handleResult(result)
-        ).catch((error: any) => {
+    initializeMsalHandler() {
+        this._redirectCallback = this.completeAuthentication();
+    }
+
+    private async completeAuthentication() {
+        try {
+            const result = await this._msalApplication.handleRedirectPromise();
+            const res = this.handleResult(result);
+            return res;
+        } catch (error) {
             if (this.isMsalError(error)) {
             if (this.isMsalError(error)) {
                 return this.error(error.errorMessage);
                 return this.error(error.errorMessage);
             } else {
             } else {
-                return this.error(error);
+                return this.error((error as Error).message);
             }
             }
-        })
+        };
     }
     }
 
 
     private handleResult(result: Msal.AuthenticationResult | null) {
     private handleResult(result: Msal.AuthenticationResult | null) {
+        const logoutState = this.retrieveState(location.href, undefined);
         if (result) {
         if (result) {
             this._account = result.account;
             this._account = result.account;
             return this.success(this.retrieveState(null, result.state));
             return this.success(this.retrieveState(null, result.state));
+        } else if (logoutState) {
+            return this.success(logoutState);
         } else {
         } else {
             return this.operationCompleted();
             return this.operationCompleted();
         }
         }
     }
     }
 
 
-    private getAccountState(state: string) {
-        if (state) {
-            const splitIndex = state.indexOf("|");
-            if (splitIndex > -1 && splitIndex + 1 < state.length) {
-                return state.substring(splitIndex + 1);
-            }
-        }
-        return state;
-    }
-
     private isMsalError(resultOrError: any): resultOrError is Msal.AuthError {
     private isMsalError(resultOrError: any): resultOrError is Msal.AuthError {
         return resultOrError?.errorCode;
         return resultOrError?.errorCode;
     }
     }
@@ -371,20 +445,29 @@ class MsalAuthorizeService implements AuthorizeService {
     private operationCompleted() {
     private operationCompleted() {
         return { status: AuthenticationResultStatus.OperationCompleted };
         return { status: AuthenticationResultStatus.OperationCompleted };
     }
     }
+
+    private debug(message: string) {
+        this._logger?.log(LogLevel.Debug, message);
+    }
+
+    private trace(message: string, data: any) {
+        this._logger?.log(LogLevel.Trace, `${message}: ${JSON.stringify(data)}`);
+    }
 }
 }
 
 
 export class AuthenticationService {
 export class AuthenticationService {
 
 
     static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal';
     static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal';
-    static _initialized: Promise<void>;
+    static _initialized: boolean;
     static instance: MsalAuthorizeService;
     static instance: MsalAuthorizeService;
 
 
-    public static async init(settings: AuthorizeServiceConfiguration) {
+    public static async init(settings: AuthorizeServiceConfiguration, jsLoggingOptions: JavaScriptLoggingOptions) {
         if (!AuthenticationService._initialized) {
         if (!AuthenticationService._initialized) {
-            AuthenticationService.instance = new MsalAuthorizeService(settings);
-            AuthenticationService._initialized = AuthenticationService.instance.initializeMsalHandler();
+            AuthenticationService.instance = new MsalAuthorizeService(settings, new Logger(jsLoggingOptions));
+            AuthenticationService.instance.initializeMsalHandler();
+            AuthenticationService._initialized = true;
         }
         }
-        return AuthenticationService._initialized;
+        return Promise.resolve();
     }
     }
 
 
     public static getUser() {
     public static getUser() {

+ 3 - 3
src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json

@@ -20,13 +20,13 @@
     "inspectpack": "^4.7.1",
     "inspectpack": "^4.7.1",
     "rimraf": "^3.0.2",
     "rimraf": "^3.0.2",
     "terser": "^5.14.2",
     "terser": "^5.14.2",
-    "ts-loader": "^9.2.5",
-    "typescript": "^4.4.2",
+    "ts-loader": "^9.3.1",
+    "typescript": "^4.8.3",
     "webpack": "^5.72.1",
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2"
     "webpack-cli": "^4.9.2"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@azure/msal-browser": "^2.16.1"
+    "@azure/msal-browser": "^2.28.3"
   },
   },
   "resolutions": {
   "resolutions": {
     "ansi-regex": "5.0.1",
     "ansi-regex": "5.0.1",

+ 17 - 19
src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock

@@ -2,19 +2,17 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
-"@azure/msal-browser@^2.16.1":
-  version "2.16.1"
-  resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.16.1.tgz#4b41afc1654eb27e090892893a2a2600af589af7"
-  integrity sha512-PcYi4seUGdtu30mOIdwk4FvbOdHFQ+fKAptEqV0tcsrvor0Hox/R0Xr0GEiBMfKkJbagLOYX7ORPRCYhZrWeqw==
+"@azure/msal-browser@^2.28.3":
+  version "2.28.3"
+  resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.28.3.tgz#7cd35e632ea74a2ef5f9939fdce8757ffb93487f"
+  integrity sha512-2SdyH2el3s8BzPURf9RK17BvvXvaMEGpLc3D9WilZcmjJqP4nStVH7Ogwr/SNTuGV48FUhqEkP0RxDvzuFJSIw==
   dependencies:
   dependencies:
-    "@azure/msal-common" "^4.5.1"
+    "@azure/msal-common" "^7.4.1"
 
 
-"@azure/msal-common@^4.5.1":
-  version "4.5.1"
-  resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-4.5.1.tgz#f35af8b634ae24aebd0906deb237c0db1afa5826"
-  integrity sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ==
-  dependencies:
-    debug "^4.1.1"
+"@azure/msal-common@^7.4.1":
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-7.4.1.tgz#204c32d247336d7e334984e599bfd63156554f83"
+  integrity sha512-zxcxg9pRdgGTS5mrRJeQvwA8aIjD8qSGzaAiz5SeTVkyhtjB0AeFnAcvBOKHv/TkswWNfYKpERxsXOAKXkXk0w==
 
 
 "@babel/code-frame@^7.14.5":
 "@babel/code-frame@^7.14.5":
   version "7.14.5"
   version "7.14.5"
@@ -2683,10 +2681,10 @@ to-regex-range@^5.0.1:
   dependencies:
   dependencies:
     is-number "^7.0.0"
     is-number "^7.0.0"
 
 
-ts-loader@^9.2.5:
-  version "9.2.5"
-  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.5.tgz#127733a5e9243bf6dafcb8aa3b8a266d8041dca9"
-  integrity sha512-al/ATFEffybdRMUIr5zMEWQdVnCGMUA9d3fXJ8dBVvBlzytPvIszoG9kZoR+94k6/i293RnVOXwMaWbXhNy9pQ==
+ts-loader@^9.3.1:
+  version "9.3.1"
+  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.1.tgz#fe25cca56e3e71c1087fe48dc67f4df8c59b22d4"
+  integrity sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==
   dependencies:
   dependencies:
     chalk "^4.1.0"
     chalk "^4.1.0"
     enhanced-resolve "^5.0.0"
     enhanced-resolve "^5.0.0"
@@ -2717,10 +2715,10 @@ type-fest@^0.20.2:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
   integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
   integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
 
 
-typescript@^4.4.2:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
-  integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
+typescript@^4.8.3:
+  version "4.8.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88"
+  integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==
 
 
 unicode-canonical-property-names-ecmascript@^1.0.4:
 unicode-canonical-property-names-ecmascript@^1.0.4:
   version "1.0.4"
   version "1.0.4"

+ 6 - 0
src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj

@@ -92,4 +92,10 @@
 
 
   <Import Project="Sdk.targets" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
   <Import Project="Sdk.targets" Sdk="Yarn.MSBuild" Condition=" '$(DotNetBuildFromSource)' != 'true'" />
 
 
+  <ItemGroup>
+    <EmbeddedResource Include="ILLink.Descriptors.xml">
+      <LogicalName>ILLink.Descriptors.xml</LogicalName>
+    </EmbeddedResource>
+  </ItemGroup>
+
 </Project>
 </Project>

+ 0 - 3
src/Components/test/testassets/BasicTestApp/wwwroot/index.html

@@ -52,9 +52,6 @@
 
 
     <!-- Used by ExternalContentPackage -->
     <!-- Used by ExternalContentPackage -->
     <script src="_content/TestContentPackage/prompt.js"></script>
     <script src="_content/TestContentPackage/prompt.js"></script>
-
-    <!-- Used by CustomElementsComponent -->
-    <script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
 </body>
 </body>
 
 
 </html>
 </html>

+ 2 - 1
src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
     <Description>ASP.NET Core common extension methods for HTTP abstractions, HTTP headers, HTTP request/response, and session state.</Description>
     <Description>ASP.NET Core common extension methods for HTTP abstractions, HTTP headers, HTTP request/response, and session state.</Description>
@@ -17,6 +17,7 @@
     <Compile Include="$(SharedSourceRoot)EndpointMetadataPopulator.cs" LinkBase="Shared"/>
     <Compile Include="$(SharedSourceRoot)EndpointMetadataPopulator.cs" LinkBase="Shared"/>
     <Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared"/>
     <Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared"/>
     <Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
     <Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)ApiExplorerTypes\*.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared"/>
     <Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared"/>
     <Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsDefaults.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsDefaults.cs" LinkBase="Shared" />

+ 55 - 8
src/Http/Http.Extensions/src/RequestDelegateFactory.cs

@@ -110,8 +110,9 @@ public static partial class RequestDelegateFactory
     private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
     private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
     private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "filterContext");
     private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "filterContext");
 
 
-    private static readonly string[] DefaultAcceptsContentType = new[] { "application/json" };
+    private static readonly string[] DefaultAcceptsAndProducesContentType = new[] { JsonConstants.JsonContentType };
     private static readonly string[] FormFileContentType = new[] { "multipart/form-data" };
     private static readonly string[] FormFileContentType = new[] { "multipart/form-data" };
+    private static readonly string[] PlaintextContentType = new[] { "text/plain" };
 
 
     /// <summary>
     /// <summary>
     /// Returns metadata inferred automatically for the <see cref="RequestDelegate"/> created by <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.
     /// Returns metadata inferred automatically for the <see cref="RequestDelegate"/> created by <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.
@@ -378,10 +379,10 @@ public static partial class RequestDelegateFactory
 
 
         if (!factoryContext.MetadataAlreadyInferred)
         if (!factoryContext.MetadataAlreadyInferred)
         {
         {
+            PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext.EndpointBuilder);
+
             // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
             // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
-            EndpointMetadataPopulator.PopulateMetadata(methodInfo,
-                factoryContext.EndpointBuilder,
-                factoryContext.Parameters);
+            EndpointMetadataPopulator.PopulateMetadata(methodInfo, factoryContext.EndpointBuilder, factoryContext.Parameters);
         }
         }
 
 
         return args;
         return args;
@@ -927,6 +928,47 @@ public static partial class RequestDelegateFactory
         return Expression.Block(localVariables, checkParamAndCallMethod);
         return Expression.Block(localVariables, checkParamAndCallMethod);
     }
     }
 
 
+    private static void PopulateBuiltInResponseTypeMetadata(Type returnType, EndpointBuilder builder)
+    {
+        if (returnType.IsByRefLike)
+        {
+            throw GetUnsupportedReturnTypeException(returnType);
+        }
+
+        if (returnType == typeof(Task) || returnType == typeof(ValueTask))
+        {
+            returnType = typeof(void);
+        }
+        else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
+        {
+            var genericTypeDefinition = returnType.IsGenericType ? returnType.GetGenericTypeDefinition() : null;
+
+            if (genericTypeDefinition == typeof(Task<>) || genericTypeDefinition == typeof(ValueTask<>))
+            {
+                returnType = returnType.GetGenericArguments()[0];
+            }
+            else
+            {
+                throw GetUnsupportedReturnTypeException(returnType);
+            }
+        }
+
+        // Skip void returns and IResults. IResults might implement IEndpointMetadataProvider but otherwise we don't know what it might do.
+        if (returnType == typeof(void) || typeof(IResult).IsAssignableFrom(returnType))
+        {
+            return;
+        }
+
+        if (returnType == typeof(string))
+        {
+            builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(type: null, statusCode: 200, PlaintextContentType));
+        }
+        else
+        {
+            builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(returnType, statusCode: 200, DefaultAcceptsAndProducesContentType));
+        }
+    }
+
     private static Expression AddResponseWritingToMethodCall(Expression methodCall, Type returnType)
     private static Expression AddResponseWritingToMethodCall(Expression methodCall, Type returnType)
     {
     {
         // Exact request delegate match
         // Exact request delegate match
@@ -1021,7 +1063,7 @@ public static partial class RequestDelegateFactory
             else
             else
             {
             {
                 // TODO: Handle custom awaitables
                 // TODO: Handle custom awaitables
-                throw new NotSupportedException($"Unsupported return type: {TypeNameHelper.GetTypeDisplayName(returnType)}");
+                throw GetUnsupportedReturnTypeException(returnType);
             }
             }
         }
         }
         else if (typeof(IResult).IsAssignableFrom(returnType))
         else if (typeof(IResult).IsAssignableFrom(returnType))
@@ -1039,8 +1081,7 @@ public static partial class RequestDelegateFactory
         }
         }
         else if (returnType.IsByRefLike)
         else if (returnType.IsByRefLike)
         {
         {
-            // Unsupported
-            throw new NotSupportedException($"Unsupported return type: {TypeNameHelper.GetTypeDisplayName(returnType)}");
+            throw GetUnsupportedReturnTypeException(returnType);
         }
         }
         else if (returnType.IsValueType)
         else if (returnType.IsValueType)
         {
         {
@@ -1849,7 +1890,7 @@ public static partial class RequestDelegateFactory
 
 
         factoryContext.JsonRequestBodyParameter = parameter;
         factoryContext.JsonRequestBodyParameter = parameter;
         factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
         factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
-        AddInferredAcceptsMetadata(factoryContext, parameter.ParameterType, DefaultAcceptsContentType);
+        AddInferredAcceptsMetadata(factoryContext, parameter.ParameterType, DefaultAcceptsAndProducesContentType);
 
 
         if (!factoryContext.AllowEmptyRequestBody)
         if (!factoryContext.AllowEmptyRequestBody)
         {
         {
@@ -2152,6 +2193,12 @@ public static partial class RequestDelegateFactory
     {
     {
         await EnsureRequestResultNotNull(result).ExecuteAsync(httpContext);
         await EnsureRequestResultNotNull(result).ExecuteAsync(httpContext);
     }
     }
+
+    private static NotSupportedException GetUnsupportedReturnTypeException(Type returnType)
+    {
+        return new NotSupportedException($"Unsupported return type: {TypeNameHelper.GetTypeDisplayName(returnType)}");
+    }
+
     private static class RequestDelegateFactoryConstants
     private static class RequestDelegateFactoryConstants
     {
     {
         public const string RouteAttribute = "Route (Attribute)";
         public const string RouteAttribute = "Route (Attribute)";

+ 42 - 6
src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

@@ -4,7 +4,6 @@
 #nullable enable
 #nullable enable
 
 
 using System.Buffers;
 using System.Buffers;
-using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using System.IO.Pipelines;
 using System.IO.Pipelines;
 using System.Linq.Expressions;
 using System.Linq.Expressions;
@@ -6033,7 +6032,7 @@ public class RequestDelegateFactoryTests : LoggedTest
     public void Create_DoesNotAddDelegateMethodInfo_AsMetadata()
     public void Create_DoesNotAddDelegateMethodInfo_AsMetadata()
     {
     {
         // Arrange
         // Arrange
-        var @delegate = () => "Hello";
+        var @delegate = () => { };
 
 
         // Act
         // Act
         var result = RequestDelegateFactory.Create(@delegate);
         var result = RequestDelegateFactory.Create(@delegate);
@@ -6043,6 +6042,30 @@ public class RequestDelegateFactoryTests : LoggedTest
         Assert.Empty(result.EndpointMetadata);
         Assert.Empty(result.EndpointMetadata);
     }
     }
 
 
+    [Fact]
+    public void Create_AddJsonResponseType_AsMetadata()
+    {
+        var @delegate = () => new object();
+        var result = RequestDelegateFactory.Create(@delegate);
+
+        var responseMetadata = Assert.IsAssignableFrom<IProducesResponseTypeMetadata>(Assert.Single(result.EndpointMetadata));
+
+        Assert.Equal("application/json", Assert.Single(responseMetadata.ContentTypes));
+        Assert.Equal(typeof(object), responseMetadata.Type);
+    }
+
+    [Fact]
+    public void Create_AddPlaintextResponseType_AsMetadata()
+    {
+        var @delegate = () => "Hello";
+        var result = RequestDelegateFactory.Create(@delegate);
+
+        var responseMetadata = Assert.IsAssignableFrom<IProducesResponseTypeMetadata>(Assert.Single(result.EndpointMetadata));
+
+        Assert.Equal("text/plain", Assert.Single(responseMetadata.ContentTypes));
+        Assert.Null(responseMetadata.Type);
+    }
+
     [Fact]
     [Fact]
     public void Create_DoesNotAddAnythingBefore_ThePassedInEndpointMetadata()
     public void Create_DoesNotAddAnythingBefore_ThePassedInEndpointMetadata()
     {
     {
@@ -6278,7 +6301,7 @@ public class RequestDelegateFactoryTests : LoggedTest
     public void Create_CombinesAllMetadata_InCorrectOrder()
     public void Create_CombinesAllMetadata_InCorrectOrder()
     {
     {
         // Arrange
         // Arrange
-        var @delegate = [Attribute1, Attribute2] (AddsCustomParameterMetadata param1) => new CountsDefaultEndpointMetadataResult();
+        var @delegate = [Attribute1, Attribute2] (AddsCustomParameterMetadata param1) => new CountsDefaultEndpointMetadataPoco();
         var options = new RequestDelegateFactoryOptions
         var options = new RequestDelegateFactoryOptions
         {
         {
             EndpointBuilder = CreateEndpointBuilder(new List<object>
             EndpointBuilder = CreateEndpointBuilder(new List<object>
@@ -6298,12 +6321,14 @@ public class RequestDelegateFactoryTests : LoggedTest
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Caller }),
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Caller }),
             // Inferred AcceptsMetadata from RDF for complex type
             // Inferred AcceptsMetadata from RDF for complex type
             m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)),
             m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)),
+            // Inferred ProducesResopnseTypeMetadata from RDF for complex type
+            m => Assert.Equal(typeof(CountsDefaultEndpointMetadataPoco), ((IProducesResponseTypeMetadata)m).Type),
             // Metadata provided by parameters implementing IEndpointParameterMetadataProvider
             // Metadata provided by parameters implementing IEndpointParameterMetadataProvider
             m => Assert.True(m is ParameterNameMetadata { Name: "param1" }),
             m => Assert.True(m is ParameterNameMetadata { Name: "param1" }),
             // Metadata provided by parameters implementing IEndpointMetadataProvider
             // Metadata provided by parameters implementing IEndpointMetadataProvider
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Parameter }),
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Parameter }),
             // Metadata provided by return type implementing IEndpointMetadataProvider
             // Metadata provided by return type implementing IEndpointMetadataProvider
-            m => Assert.True(m is MetadataCountMetadata { Count: 4 }));
+            m => Assert.True(m is MetadataCountMetadata { Count: 5 }));
     }
     }
 
 
     [Fact]
     [Fact]
@@ -6369,7 +6394,7 @@ public class RequestDelegateFactoryTests : LoggedTest
     public void InferMetadata_ThenCreate_CombinesAllMetadata_InCorrectOrder()
     public void InferMetadata_ThenCreate_CombinesAllMetadata_InCorrectOrder()
     {
     {
         // Arrange
         // Arrange
-        var @delegate = [Attribute1, Attribute2] (AddsCustomParameterMetadata param1) => new CountsDefaultEndpointMetadataResult();
+        var @delegate = [Attribute1, Attribute2] (AddsCustomParameterMetadata param1) => new CountsDefaultEndpointMetadataPoco();
         var options = new RequestDelegateFactoryOptions
         var options = new RequestDelegateFactoryOptions
         {
         {
             EndpointBuilder = CreateEndpointBuilder(),
             EndpointBuilder = CreateEndpointBuilder(),
@@ -6384,12 +6409,14 @@ public class RequestDelegateFactoryTests : LoggedTest
         Assert.Collection(result.EndpointMetadata,
         Assert.Collection(result.EndpointMetadata,
             // Inferred AcceptsMetadata from RDF for complex type
             // Inferred AcceptsMetadata from RDF for complex type
             m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)),
             m => Assert.True(m is AcceptsMetadata am && am.RequestType == typeof(AddsCustomParameterMetadata)),
+            // Inferred ProducesResopnseTypeMetadata from RDF for complex type
+            m => Assert.Equal(typeof(CountsDefaultEndpointMetadataPoco), ((IProducesResponseTypeMetadata)m).Type),
             // Metadata provided by parameters implementing IEndpointParameterMetadataProvider
             // Metadata provided by parameters implementing IEndpointParameterMetadataProvider
             m => Assert.True(m is ParameterNameMetadata { Name: "param1" }),
             m => Assert.True(m is ParameterNameMetadata { Name: "param1" }),
             // Metadata provided by parameters implementing IEndpointMetadataProvider
             // Metadata provided by parameters implementing IEndpointMetadataProvider
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Parameter }),
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Parameter }),
             // Metadata provided by return type implementing IEndpointMetadataProvider
             // Metadata provided by return type implementing IEndpointMetadataProvider
-            m => Assert.True(m is MetadataCountMetadata { Count: 3 }),
+            m => Assert.True(m is MetadataCountMetadata { Count: 4 }),
             // Entry-specific metadata added after a call to InferMetadata
             // Entry-specific metadata added after a call to InferMetadata
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Caller }));
             m => Assert.True(m is CustomEndpointMetadata { Source: MetadataSource.Caller }));
     }
     }
@@ -6635,6 +6662,15 @@ public class RequestDelegateFactoryTests : LoggedTest
         public Task ExecuteAsync(HttpContext httpContext) => Task.CompletedTask;
         public Task ExecuteAsync(HttpContext httpContext) => Task.CompletedTask;
     }
     }
 
 
+    private class CountsDefaultEndpointMetadataPoco : IEndpointMetadataProvider
+    {
+        public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
+        {
+            var currentMetadataCount = builder.Metadata.Count;
+            builder.Metadata.Add(new MetadataCountMetadata { Count = currentMetadataCount });
+        }
+    }
+
     private class RemovesAcceptsParameterMetadata : IEndpointParameterMetadataProvider
     private class RemovesAcceptsParameterMetadata : IEndpointParameterMetadataProvider
     {
     {
         public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
         public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)

+ 1 - 0
src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs

@@ -85,6 +85,7 @@ public class AcceptedAtRouteResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 3 - 1
src/Http/Http.Results/test/AcceptedResultTests.cs

@@ -41,7 +41,9 @@ public class AcceptedResultTests
         PopulateMetadata<Accepted>(((Delegate)MyApi).GetMethodInfo(), builder);
         PopulateMetadata<Accepted>(((Delegate)MyApi).GetMethodInfo(), builder);
 
 
         // Assert
         // Assert
-        Assert.Contains(builder.Metadata, m => m is ProducesResponseTypeMetadata { StatusCode: StatusCodes.Status202Accepted });
+        var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
+        Assert.Equal(StatusCodes.Status202Accepted, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/BadRequestResultTests.cs

@@ -56,6 +56,7 @@ public class BadRequestResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status400BadRequest, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status400BadRequest, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/ConflictResultTests.cs

@@ -57,6 +57,7 @@ public class ConflictResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status409Conflict, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status409Conflict, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/CreatedAtRouteResultTests.cs

@@ -81,6 +81,7 @@ public partial class CreatedAtRouteResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/CreatedResultTests.cs

@@ -74,6 +74,7 @@ public class CreatedResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status201Created, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/NoContentResultTests.cs

@@ -53,6 +53,7 @@ public class NoContentResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status204NoContent, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status204NoContent, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/NotFoundResultTests.cs

@@ -52,6 +52,7 @@ public class NotFoundResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status404NotFound, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status404NotFound, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/OkResultTests.cs

@@ -55,6 +55,7 @@ public class OkResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status200OK, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status200OK, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 1 - 0
src/Http/Http.Results/test/UnprocessableEntityResultTests.cs

@@ -56,6 +56,7 @@ public class UnprocessableEntityResultTests
         // Assert
         // Assert
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         var producesResponseTypeMetadata = builder.Metadata.OfType<ProducesResponseTypeMetadata>().Last();
         Assert.Equal(StatusCodes.Status422UnprocessableEntity, producesResponseTypeMetadata.StatusCode);
         Assert.Equal(StatusCodes.Status422UnprocessableEntity, producesResponseTypeMetadata.StatusCode);
+        Assert.Equal(typeof(void), producesResponseTypeMetadata.Type);
     }
     }
 
 
     [Fact]
     [Fact]

+ 2 - 0
src/OpenApi/src/OpenApiGenerator.cs

@@ -193,6 +193,8 @@ internal sealed class OpenApiGenerator
         foreach (var annotation in eligibileAnnotations)
         foreach (var annotation in eligibileAnnotations)
         {
         {
             var statusCode = annotation.Key.ToString(CultureInfo.InvariantCulture);
             var statusCode = annotation.Key.ToString(CultureInfo.InvariantCulture);
+
+            // TODO: Use the discarded response Type for schema generation
             var (_, contentTypes) = annotation.Value;
             var (_, contentTypes) = annotation.Value;
             var responseContent = new Dictionary<string, OpenApiMediaType>();
             var responseContent = new Dictionary<string, OpenApiMediaType>();
 
 

+ 9 - 1
src/OpenApi/test/OpenApiRouteHandlerBuilderExtensionTests.cs

@@ -136,8 +136,16 @@ public class OpenApiRouteHandlerBuilderExtensionTests
         var groupDataSource = Assert.Single(builder.DataSources);
         var groupDataSource = Assert.Single(builder.DataSources);
         var endpoint = Assert.Single(groupDataSource.Endpoints);
         var endpoint = Assert.Single(groupDataSource.Endpoints);
         var operation = endpoint.Metadata.GetMetadata<OpenApiOperation>();
         var operation = endpoint.Metadata.GetMetadata<OpenApiOperation>();
+
         Assert.NotNull(operation);
         Assert.NotNull(operation);
-        Assert.Equal("201", operation.Responses.Keys.SingleOrDefault());
+        Assert.Equal(2, operation.Responses.Count);
+
+        var defaultOperation = operation.Responses["200"];
+        Assert.True(defaultOperation.Content.ContainsKey("text/plain"));
+
+        var annotatedOperation = operation.Responses["201"];
+        // Produces doesn't special case string??
+        Assert.True(annotatedOperation.Content.ContainsKey("application/json"));
     }
     }
 
 
     [Fact]
     [Fact]

+ 22 - 2
src/Servers/Kestrel/Core/src/Internal/TlsConnectionFeature.cs

@@ -4,6 +4,7 @@
 using System.Net.Security;
 using System.Net.Security;
 using System.Security.Authentication;
 using System.Security.Authentication;
 using System.Security.Cryptography.X509Certificates;
 using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.Connections;
 using Microsoft.AspNetCore.Connections.Features;
 using Microsoft.AspNetCore.Connections.Features;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
@@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
 internal sealed class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature, ITlsHandshakeFeature
 internal sealed class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProtocolFeature, ITlsHandshakeFeature
 {
 {
     private readonly SslStream _sslStream;
     private readonly SslStream _sslStream;
+    private readonly ConnectionContext _context;
     private X509Certificate2? _clientCert;
     private X509Certificate2? _clientCert;
     private ReadOnlyMemory<byte>? _applicationProtocol;
     private ReadOnlyMemory<byte>? _applicationProtocol;
     private SslProtocols? _protocol;
     private SslProtocols? _protocol;
@@ -24,14 +26,19 @@ internal sealed class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicat
     private int? _keyExchangeStrength;
     private int? _keyExchangeStrength;
     private Task<X509Certificate2?>? _clientCertTask;
     private Task<X509Certificate2?>? _clientCertTask;
 
 
-    public TlsConnectionFeature(SslStream sslStream)
+    public TlsConnectionFeature(SslStream sslStream, ConnectionContext context)
     {
     {
         if (sslStream is null)
         if (sslStream is null)
         {
         {
             throw new ArgumentNullException(nameof(sslStream));
             throw new ArgumentNullException(nameof(sslStream));
         }
         }
+        if (context is null)
+        {
+            throw new ArgumentNullException(nameof(context));
+        }
 
 
         _sslStream = sslStream;
         _sslStream = sslStream;
+        _context = context;
     }
     }
 
 
     internal bool AllowDelayedClientCertificateNegotation { get; set; }
     internal bool AllowDelayedClientCertificateNegotation { get; set; }
@@ -122,7 +129,20 @@ internal sealed class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicat
 
 
     private async Task<X509Certificate2?> GetClientCertificateAsyncCore(CancellationToken cancellationToken)
     private async Task<X509Certificate2?> GetClientCertificateAsyncCore(CancellationToken cancellationToken)
     {
     {
-        await _sslStream.NegotiateClientCertificateAsync(cancellationToken);
+        try
+        {
+            await _sslStream.NegotiateClientCertificateAsync(cancellationToken);
+        }
+        catch
+        {
+            // We can't tell which exceptions are fatal or recoverable. Consider them all recoverable only given a new connection
+            // and close the connection gracefully to avoid over-caching and affecting future requests on this connection.
+            // This allows recovery by starting a new connection. The close is graceful to allow the server to
+            // send an error response like 401. https://github.com/dotnet/aspnetcore/issues/41369
+            _context.Features.Get<IConnectionLifetimeNotificationFeature>()?.RequestClose();
+            throw;
+        }
+
         return ClientCertificate;
         return ClientCertificate;
     }
     }
 
 

+ 1 - 1
src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs

@@ -138,7 +138,7 @@ internal sealed class HttpsConnectionMiddleware
             context.Features.Get<IMemoryPoolFeature>()?.MemoryPool ?? MemoryPool<byte>.Shared);
             context.Features.Get<IMemoryPoolFeature>()?.MemoryPool ?? MemoryPool<byte>.Shared);
         var sslStream = sslDuplexPipe.Stream;
         var sslStream = sslDuplexPipe.Stream;
 
 
-        var feature = new Core.Internal.TlsConnectionFeature(sslStream);
+        var feature = new Core.Internal.TlsConnectionFeature(sslStream, context);
         // Set the mode if options were used. If the callback is used it will set the mode later.
         // Set the mode if options were used. If the callback is used it will set the mode later.
         feature.AllowDelayedClientCertificateNegotation =
         feature.AllowDelayedClientCertificateNegotation =
             _options?.ClientCertificateMode == ClientCertificateMode.DelayCertificate;
             _options?.ClientCertificateMode == ClientCertificateMode.DelayCertificate;

+ 55 - 52
src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs

@@ -539,7 +539,6 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
             listenOptions.UseHttps(options =>
             listenOptions.UseHttps(options =>
             {
             {
                 options.ServerCertificate = _x509Certificate2;
                 options.ServerCertificate = _x509Certificate2;
-                options.SslProtocols = SslProtocols.Tls12; // Linux doesn't support renegotiate on TLS1.3 yet. https://github.com/dotnet/runtime/issues/55757
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.AllowAnyClientCertificate();
                 options.AllowAnyClientCertificate();
             });
             });
@@ -626,7 +625,6 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
                     return ValueTask.FromResult(new SslServerAuthenticationOptions()
                     return ValueTask.FromResult(new SslServerAuthenticationOptions()
                     {
                     {
                         ServerCertificate = _x509Certificate2,
                         ServerCertificate = _x509Certificate2,
-                        EnabledSslProtocols = SslProtocols.Tls12, // Linux doesn't support renegotiate on TLS1.3 yet. https://github.com/dotnet/runtime/issues/55757
                         ClientCertificateRequired = false,
                         ClientCertificateRequired = false,
                         RemoteCertificateValidationCallback = (_, _, _, _) => true,
                         RemoteCertificateValidationCallback = (_, _, _, _) => true,
                     });
                     });
@@ -670,7 +668,6 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
             listenOptions.UseHttps(options =>
             listenOptions.UseHttps(options =>
             {
             {
                 options.ServerCertificate = _x509Certificate2;
                 options.ServerCertificate = _x509Certificate2;
-                options.SslProtocols = SslProtocols.Tls12; // Linux doesn't support renegotiate on TLS1.3 yet. https://github.com/dotnet/runtime/issues/55757
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.AllowAnyClientCertificate();
                 options.AllowAnyClientCertificate();
             });
             });
@@ -786,60 +783,13 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
     // then the connection is aborted.
     // then the connection is aborted.
     [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
     [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
     [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
     [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
-    public async Task RenegotiateForClientCertificateOnPostWithoutBufferingThrows_TLS12()
+    public async Task RenegotiateForClientCertificateOnPostWithoutBufferingThrows()
     {
     {
         void ConfigureListenOptions(ListenOptions listenOptions)
         void ConfigureListenOptions(ListenOptions listenOptions)
         {
         {
             listenOptions.Protocols = HttpProtocols.Http1;
             listenOptions.Protocols = HttpProtocols.Http1;
             listenOptions.UseHttps(options =>
             listenOptions.UseHttps(options =>
             {
             {
-                options.SslProtocols = SslProtocols.Tls12;
-                options.ServerCertificate = _x509Certificate2;
-                options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
-                options.AllowAnyClientCertificate();
-            });
-        }
-
-        // Under 4kb can sometimes work because it fits into Kestrel's header parsing buffer.
-        var expectedBody = new string('a', 1024 * 4);
-
-        await using var server = new TestServer(async context =>
-        {
-            var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
-            Assert.NotNull(tlsFeature);
-            Assert.Null(tlsFeature.ClientCertificate);
-            Assert.Null(context.Connection.ClientCertificate);
-
-            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Connection.GetClientCertificateAsync());
-            Assert.Equal("Client stream needs to be drained before renegotiation.", ex.Message);
-            Assert.Null(tlsFeature.ClientCertificate);
-            Assert.Null(context.Connection.ClientCertificate);
-        }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
-
-        using var connection = server.CreateConnection();
-        // SslStream is used to ensure the certificate is actually passed to the server
-        // HttpClient might not send the certificate because it is invalid or it doesn't match any
-        // of the certificate authorities sent by the server in the SSL handshake.
-        // Use a random host name to avoid the TLS session resumption cache.
-        var stream = OpenSslStreamWithCert(connection.Stream);
-        await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
-        await AssertConnectionResult(stream, true, expectedBody);
-    }
-
-    [ConditionalFact]
-    // TLS 1.3 uses a new client cert negotiation extension that doesn't cause the connection to abort
-    // for this error.
-    [MinimumOSVersion(OperatingSystems.Windows, "10.0.20145")] // Needs a preview version with TLS 1.3 enabled.
-    [OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "https://github.com/dotnet/runtime/issues/55757")]
-    [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
-    public async Task RenegotiateForClientCertificateOnPostWithoutBufferingThrows_TLS13()
-    {
-        void ConfigureListenOptions(ListenOptions listenOptions)
-        {
-            listenOptions.Protocols = HttpProtocols.Http1;
-            listenOptions.UseHttps(options =>
-            {
-                options.SslProtocols = SslProtocols.Tls13;
                 options.ServerCertificate = _x509Certificate2;
                 options.ServerCertificate = _x509Certificate2;
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.AllowAnyClientCertificate();
                 options.AllowAnyClientCertificate();
@@ -977,7 +927,6 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
             listenOptions.UseHttps(options =>
             listenOptions.UseHttps(options =>
             {
             {
                 options.ServerCertificate = _x509Certificate2;
                 options.ServerCertificate = _x509Certificate2;
-                options.SslProtocols = SslProtocols.Tls12; // Linux doesn't support renegotiate on TLS1.3 yet. https://github.com/dotnet/runtime/issues/55757
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
                 options.AllowAnyClientCertificate();
                 options.AllowAnyClientCertificate();
             });
             });
@@ -1013,6 +962,60 @@ public class HttpsConnectionMiddlewareTests : LoggedTest
         await AssertConnectionResult(stream, true, expectedBody);
         await AssertConnectionResult(stream, true, expectedBody);
     }
     }
 
 
+    [ConditionalFact]
+    [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
+    [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
+    public async Task RenegotationFailureCausesConnectionClose()
+    {
+        void ConfigureListenOptions(ListenOptions listenOptions)
+        {
+            listenOptions.Protocols = HttpProtocols.Http1;
+            listenOptions.UseHttps(options =>
+            {
+                options.ServerCertificate = _x509Certificate2;
+                options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
+                options.AllowAnyClientCertificate();
+            });
+        }
+
+        var expectedBody = new string('a', 1024 * 4);
+
+        await using var server = new TestServer(async context =>
+        {
+            var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
+            Assert.NotNull(tlsFeature);
+            Assert.Null(tlsFeature.ClientCertificate);
+            Assert.Null(context.Connection.ClientCertificate);
+
+            // Request the client cert while there's still body data in the buffers
+            var ioe = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Connection.GetClientCertificateAsync());
+            Assert.Equal("Client stream needs to be drained before renegotiation.", ioe.Message);
+
+            context.Response.ContentLength = 11;
+            await context.Response.WriteAsync("hello world");
+
+        }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
+
+        using var connection = server.CreateConnection();
+        // SslStream is used to ensure the certificate is actually passed to the server
+        // HttpClient might not send the certificate because it is invalid or it doesn't match any
+        // of the certificate authorities sent by the server in the SSL handshake.
+        // Use a random host name to avoid the TLS session resumption cache.
+        var stream = OpenSslStreamWithCert(connection.Stream);
+        await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
+
+        var request = Encoding.UTF8.GetBytes($"POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: {expectedBody.Length}\r\n\r\n{expectedBody}");
+        await stream.WriteAsync(request, 0, request.Length).DefaultTimeout();
+        var reader = new StreamReader(stream);
+        Assert.Equal("HTTP/1.1 200 OK", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.Equal("Content-Length: 11", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.Equal("Connection: close", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.StartsWith("Date: ", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.Equal("", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.Equal("hello world", await reader.ReadLineAsync().DefaultTimeout());
+        Assert.Null(await reader.ReadLineAsync().DefaultTimeout());
+    }
+
     [Fact]
     [Fact]
     public async Task HttpsSchemePassedToRequestFeature()
     public async Task HttpsSchemePassedToRequestFeature()
     {
     {

+ 14 - 18
src/Shared/ApiExplorerTypes/ProducesResponseTypeMetadata.cs

@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // The .NET Foundation licenses this file to you under the MIT license.
 
 
-using System;
 using System.Linq;
 using System.Linq;
 using Microsoft.AspNetCore.Http.Metadata;
 using Microsoft.AspNetCore.Http.Metadata;
 using Microsoft.Net.Http.Headers;
 using Microsoft.Net.Http.Headers;
@@ -20,11 +19,11 @@ internal sealed class ProducesResponseTypeMetadata : IProducesResponseTypeMetada
     /// </summary>
     /// </summary>
     /// <param name="statusCode">The HTTP response status code.</param>
     /// <param name="statusCode">The HTTP response status code.</param>
     public ProducesResponseTypeMetadata(int statusCode)
     public ProducesResponseTypeMetadata(int statusCode)
-        : this(typeof(void), statusCode)
+        : this(typeof(void), statusCode, Enumerable.Empty<string>())
     {
     {
-        IsResponseTypeSetByDefault = true;
     }
     }
 
 
+    // Only for internal use where validation is unnecessary.
     /// <summary>
     /// <summary>
     /// Initializes an instance of <see cref="ProducesResponseTypeMetadata"/>.
     /// Initializes an instance of <see cref="ProducesResponseTypeMetadata"/>.
     /// </summary>
     /// </summary>
@@ -34,7 +33,6 @@ internal sealed class ProducesResponseTypeMetadata : IProducesResponseTypeMetada
     {
     {
         Type = type ?? throw new ArgumentNullException(nameof(type));
         Type = type ?? throw new ArgumentNullException(nameof(type));
         StatusCode = statusCode;
         StatusCode = statusCode;
-        IsResponseTypeSetByDefault = false;
         _contentTypes = Enumerable.Empty<string>();
         _contentTypes = Enumerable.Empty<string>();
     }
     }
 
 
@@ -54,7 +52,6 @@ internal sealed class ProducesResponseTypeMetadata : IProducesResponseTypeMetada
 
 
         Type = type ?? throw new ArgumentNullException(nameof(type));
         Type = type ?? throw new ArgumentNullException(nameof(type));
         StatusCode = statusCode;
         StatusCode = statusCode;
-        IsResponseTypeSetByDefault = false;
 
 
         MediaTypeHeaderValue.Parse(contentType);
         MediaTypeHeaderValue.Parse(contentType);
         for (var i = 0; i < additionalContentTypes.Length; i++)
         for (var i = 0; i < additionalContentTypes.Length; i++)
@@ -65,30 +62,29 @@ internal sealed class ProducesResponseTypeMetadata : IProducesResponseTypeMetada
         _contentTypes = GetContentTypes(contentType, additionalContentTypes);
         _contentTypes = GetContentTypes(contentType, additionalContentTypes);
     }
     }
 
 
+    // Only for internal use where validation is unnecessary.
+    private ProducesResponseTypeMetadata(Type? type, int statusCode, IEnumerable<string> contentTypes)
+    {
+
+        Type = type;
+        StatusCode = statusCode;
+        _contentTypes = contentTypes;
+    }
+
     /// <summary>
     /// <summary>
     /// Gets or sets the type of the value returned by an action.
     /// Gets or sets the type of the value returned by an action.
     /// </summary>
     /// </summary>
-    public Type Type { get; set; }
+    public Type? Type { get; set; }
 
 
     /// <summary>
     /// <summary>
     /// Gets or sets the HTTP status code of the response.
     /// Gets or sets the HTTP status code of the response.
     /// </summary>
     /// </summary>
     public int StatusCode { get; set; }
     public int StatusCode { get; set; }
 
 
-    /// <summary>
-    /// Used to distinguish a `Type` set by default in the constructor versus
-    /// one provided by the user.
-    ///
-    /// When <see langword="false"/>, then <see cref="Type"/> is set by user.
-    ///
-    /// When <see langword="true"/>, then <see cref="Type"/> is set by by
-    /// default in the constructor
-    /// </summary>
-    /// <value></value>
-    internal bool IsResponseTypeSetByDefault { get; }
-
     public IEnumerable<string> ContentTypes => _contentTypes;
     public IEnumerable<string> ContentTypes => _contentTypes;
 
 
+    internal static ProducesResponseTypeMetadata CreateUnvalidated(Type? type, int statusCode, IEnumerable<string> contentTypes) => new(type, statusCode, contentTypes);
+
     private static List<string> GetContentTypes(string contentType, string[] additionalContentTypes)
     private static List<string> GetContentTypes(string contentType, string[] additionalContentTypes)
     {
     {
         var contentTypes = new List<string>(additionalContentTypes.Length + 1);
         var contentTypes = new List<string>(additionalContentTypes.Length + 1);

Some files were not shown because too many files changed in this diff