From 7d2218331e4c10e93d2069fd99a1b7f8cd8e8fee Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Thu, 24 Jun 2021 10:25:42 -0700
Subject: [PATCH 1/6] Separate IResult based results from ActionResults

---
 AspNetCore.sln                                |   33 +
 eng/ProjectReferences.props                   |    1 +
 eng/SharedFramework.Local.props               |    1 +
 .../Http.Results/src/AcceptedAtRouteResult.cs |   68 +
 src/Http/Http.Results/src/AcceptedResult.cs   |   74 +
 .../src/BadRequestObjectResult.cs             |   13 +
 src/Http/Http.Results/src/BadRequestResult.cs |   12 +
 src/Http/Http.Results/src/ChallengeResult.cs  |  120 ++
 .../Http.Results/src/ConflictObjectResult.cs  |   13 +
 src/Http/Http.Results/src/ConflictResult.cs   |   12 +
 src/Http/Http.Results/src/ContentResult.cs    |   75 +
 .../Http.Results/src/CreatedAtRouteResult.cs  |   68 +
 src/Http/Http.Results/src/CreatedResult.cs    |   57 +
 .../Http.Results/src/FileContentResult.cs     |   74 +
 src/Http/Http.Results/src/FileResult.cs       |   93 ++
 src/Http/Http.Results/src/FileStreamResult.cs |  110 ++
 src/Http/Http.Results/src/ForbidResult.cs     |  125 ++
 src/Http/Http.Results/src/JsonResult.cs       |   78 +
 .../Http.Results/src/LocalRedirectResult.cs   |  111 ++
 .../Microsoft.AspNetCore.Http.Results.csproj  |   31 +
 src/Http/Http.Results/src/NoContentResult.cs  |   12 +
 .../Http.Results/src/NotFoundObjectResult.cs  |   13 +
 src/Http/Http.Results/src/NotFoundResult.cs   |   12 +
 src/Http/Http.Results/src/ObjectResult.cs     |   65 +
 src/Http/Http.Results/src/OkObjectResult.cs   |   13 +
 src/Http/Http.Results/src/OkResult.cs         |   12 +
 .../Http.Results/src/PhyiscalFileResult.cs    |  123 ++
 .../Http.Results/src/PublicAPI.Shipped.txt    |    1 +
 .../Http.Results/src/PublicAPI.Unshipped.txt  |  104 ++
 src/Http/Http.Results/src/RedirectResult.cs   |  107 ++
 .../Http.Results/src/RedirectToRouteResult.cs |  194 +++
 src/Http/Http.Results/src/Results.cs          | 1465 +++++++++++++++++
 src/Http/Http.Results/src/SignInResult.cs     |   97 ++
 src/Http/Http.Results/src/SignOutResult.cs    |  127 ++
 src/Http/Http.Results/src/StatusCodeResult.cs |   51 +
 .../Http.Results/src/UnauthorizedResult.cs    |   12 +
 .../src/UnprocessableEntityObjectResult.cs    |   13 +
 .../src/UnprocessableEntityResult.cs          |   12 +
 .../Http.Results/src/VirtualFileResult.cs     |  129 ++
 .../test/AcceptedAtRouteResultTests.cs        |  120 ++
 .../Http.Results/test/AcceptedResultTests.cs  |   61 +
 .../test/BadRequestObjectResultTests.cs       |   22 +
 .../test/BadRequestResultTests.cs             |   20 +
 .../Http.Results/test/ChallengeResultTest.cs  |   62 +
 .../test/ConflictObjectResultTest.cs          |   22 +
 .../Http.Results/test/ConflictResultTest.cs   |   20 +
 .../Http.Results/test/ContentResultTest.cs    |  152 ++
 .../test/CreatedAtRouteResultTests.cs         |   93 ++
 .../Http.Results/test/CreatedResultTest.cs    |   79 +
 .../test/FileContentResultTest.cs             |   38 +
 .../Http.Results/test/FileStreamResultTest.cs |   86 +
 .../Http.Results/test/ForbidResultTest.cs     |  129 ++
 .../test/LocalRedirectResultTest.cs           |  138 ++
 ...osoft.AspNetCore.Http.Results.Tests.csproj |   15 +
 .../test/NotFoundObjectResultTest.cs          |   67 +
 .../Http.Results/test/NotFoundResultTests.cs  |   20 +
 .../Http.Results/test/ObjectResultTests.cs    |   65 +
 .../Http.Results/test/OkObjectResultTest.cs   |   46 +
 src/Http/Http.Results/test/OkResultTest.cs    |   37 +
 .../test/PhysicalFileResultTest.cs            |   41 +
 .../Http.Results/test/RedirectResultTest.cs}  |   45 +-
 .../test/RedirectToRouteResultTest.cs         |  111 ++
 .../Http.Results/test/SignInResultTest.cs     |   95 ++
 .../Http.Results/test/SignOutResultTest.cs    |   95 ++
 .../test/StatusCodeResultTests.cs             |   45 +
 .../Http.Results/test/TestLinkGenerator.cs    |   29 +
 .../test/UnauthorizedResultTests.cs           |   20 +
 .../UnprocessableEntityObjectResultTests.cs   |   22 +
 .../test/UnprocessableEntityResultTests.cs    |   20 +
 .../test/VirtualFileResultTest.cs             |   25 +
 src/Http/HttpAbstractions.slnf                |    2 +
 .../StaticFiles/src/LoggerExtensions.cs       |   26 +-
 src/Mvc/Mvc.Core/src/ChallengeResult.cs       |   16 +-
 src/Mvc/Mvc.Core/src/ContentResult.cs         |   48 +-
 src/Mvc/Mvc.Core/src/FileContentResult.cs     |   43 +-
 src/Mvc/Mvc.Core/src/ForbidResult.cs          |   16 +-
 .../Infrastructure/ContentResultExecutor.cs   |    5 +-
 .../Infrastructure/FileResultExecutorBase.cs  |  373 +----
 .../SystemTextJsonResultExecutor.cs           |    4 +-
 src/Mvc/Mvc.Core/src/JsonResult.cs            |   13 +-
 .../src/Microsoft.AspNetCore.Mvc.Core.csproj  |    2 +
 .../Mvc.Core/src/MvcCoreLoggerExtensions.cs   |    2 +-
 src/Mvc/Mvc.Core/src/PhysicalFileResult.cs    |   52 +-
 src/Mvc/Mvc.Core/src/RedirectResult.cs        |   33 +-
 src/Mvc/Mvc.Core/src/SignInResult.cs          |   16 +-
 src/Mvc/Mvc.Core/src/VirtualFileResult.cs     |   39 +-
 src/Mvc/Mvc.Core/test/AcceptedResultTests.cs  |    4 +-
 src/Mvc/Mvc.Core/test/ChallengeResultTest.cs  |   37 -
 src/Mvc/Mvc.Core/test/ContentResultTest.cs    |   77 -
 src/Mvc/Mvc.Core/test/ControllerBaseTest.cs   |    3 +-
 .../test/FileContentActionResultTest.cs       |  155 --
 src/Mvc/Mvc.Core/test/FileContentResult.cs    |  101 --
 .../Mvc.Core/test/FileContentResultTest.cs    |   44 +
 ...eResultTest.cs => FileResultHelperTest.cs} |   17 +-
 src/Mvc/Mvc.Core/test/FileStreamResultTest.cs |  589 +------
 .../ResponseContentTypeHelperTest.cs          |    9 +-
 .../Microsoft.AspNetCore.Mvc.Core.Test.csproj |    1 +
 .../test/PhysicalFileActionResultTest.cs      |  204 ---
 src/Mvc/Mvc.Core/test/PhysicalFileResult.cs   |  173 --
 .../Mvc.Core/test/PhysicalFileResultTest.cs   |   66 +
 src/Mvc/Mvc.Core/test/RedirectResultTest.cs   |   98 +-
 .../test/Routing/UrlHelperExtensionsTest.cs   |    5 +-
 src/Mvc/Mvc.Core/test/SignInResultTest.cs     |   64 -
 .../test/VirtualFileActionResultTest .cs      |  213 ---
 .../Mvc.Core/test/VirtualFileResultTest.cs    |  206 +--
 ...osoft.AspNetCore.Mvc.NewtonsoftJson.csproj |    2 +-
 .../src/NewtonsoftJsonResultExecutor.cs       |    4 +-
 .../src/ViewComponentResultExecutor.cs        |    5 +-
 src/Mvc/Mvc.ViewFeatures/src/ViewExecutor.cs  |    5 +-
 .../ResponseContentTypeHelper.cs              |   35 +-
 src/Shared/ResultsHelpers/FileResultHelper.cs |  403 +++++
 src/Shared/ResultsHelpers/FileResultInfo.cs   |   21 +
 .../ResultsHelpers/FileResultLogging.cs       |   19 +
 src/Shared/ResultsHelpers/SharedUrlHelper.cs  |   93 ++
 .../FileContentResultTestBase.cs}             |  214 +--
 .../ResultsTests/FileStreamResultTestBase.cs  |  463 ++++++
 .../PhysicalFileResultTestBase.cs}            |  278 ++--
 .../ResultsTests/RedirectResultTestBase.cs}   |   57 +-
 .../VirtualFileResultTestBase.cs}             |  428 ++---
 119 files changed, 7214 insertions(+), 3140 deletions(-)
 create mode 100644 src/Http/Http.Results/src/AcceptedAtRouteResult.cs
 create mode 100644 src/Http/Http.Results/src/AcceptedResult.cs
 create mode 100644 src/Http/Http.Results/src/BadRequestObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/BadRequestResult.cs
 create mode 100644 src/Http/Http.Results/src/ChallengeResult.cs
 create mode 100644 src/Http/Http.Results/src/ConflictObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/ConflictResult.cs
 create mode 100644 src/Http/Http.Results/src/ContentResult.cs
 create mode 100644 src/Http/Http.Results/src/CreatedAtRouteResult.cs
 create mode 100644 src/Http/Http.Results/src/CreatedResult.cs
 create mode 100644 src/Http/Http.Results/src/FileContentResult.cs
 create mode 100644 src/Http/Http.Results/src/FileResult.cs
 create mode 100644 src/Http/Http.Results/src/FileStreamResult.cs
 create mode 100644 src/Http/Http.Results/src/ForbidResult.cs
 create mode 100644 src/Http/Http.Results/src/JsonResult.cs
 create mode 100644 src/Http/Http.Results/src/LocalRedirectResult.cs
 create mode 100644 src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj
 create mode 100644 src/Http/Http.Results/src/NoContentResult.cs
 create mode 100644 src/Http/Http.Results/src/NotFoundObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/NotFoundResult.cs
 create mode 100644 src/Http/Http.Results/src/ObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/OkObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/OkResult.cs
 create mode 100644 src/Http/Http.Results/src/PhyiscalFileResult.cs
 create mode 100644 src/Http/Http.Results/src/PublicAPI.Shipped.txt
 create mode 100644 src/Http/Http.Results/src/PublicAPI.Unshipped.txt
 create mode 100644 src/Http/Http.Results/src/RedirectResult.cs
 create mode 100644 src/Http/Http.Results/src/RedirectToRouteResult.cs
 create mode 100644 src/Http/Http.Results/src/Results.cs
 create mode 100644 src/Http/Http.Results/src/SignInResult.cs
 create mode 100644 src/Http/Http.Results/src/SignOutResult.cs
 create mode 100644 src/Http/Http.Results/src/StatusCodeResult.cs
 create mode 100644 src/Http/Http.Results/src/UnauthorizedResult.cs
 create mode 100644 src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
 create mode 100644 src/Http/Http.Results/src/UnprocessableEntityResult.cs
 create mode 100644 src/Http/Http.Results/src/VirtualFileResult.cs
 create mode 100644 src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
 create mode 100644 src/Http/Http.Results/test/AcceptedResultTests.cs
 create mode 100644 src/Http/Http.Results/test/BadRequestObjectResultTests.cs
 create mode 100644 src/Http/Http.Results/test/BadRequestResultTests.cs
 create mode 100644 src/Http/Http.Results/test/ChallengeResultTest.cs
 create mode 100644 src/Http/Http.Results/test/ConflictObjectResultTest.cs
 create mode 100644 src/Http/Http.Results/test/ConflictResultTest.cs
 create mode 100644 src/Http/Http.Results/test/ContentResultTest.cs
 create mode 100644 src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
 create mode 100644 src/Http/Http.Results/test/CreatedResultTest.cs
 create mode 100644 src/Http/Http.Results/test/FileContentResultTest.cs
 create mode 100644 src/Http/Http.Results/test/FileStreamResultTest.cs
 create mode 100644 src/Http/Http.Results/test/ForbidResultTest.cs
 create mode 100644 src/Http/Http.Results/test/LocalRedirectResultTest.cs
 create mode 100644 src/Http/Http.Results/test/Microsoft.AspNetCore.Http.Results.Tests.csproj
 create mode 100644 src/Http/Http.Results/test/NotFoundObjectResultTest.cs
 create mode 100644 src/Http/Http.Results/test/NotFoundResultTests.cs
 create mode 100644 src/Http/Http.Results/test/ObjectResultTests.cs
 create mode 100644 src/Http/Http.Results/test/OkObjectResultTest.cs
 create mode 100644 src/Http/Http.Results/test/OkResultTest.cs
 create mode 100644 src/Http/Http.Results/test/PhysicalFileResultTest.cs
 rename src/{Mvc/Mvc.Core/test/RedirectActionResultTest.cs => Http/Http.Results/test/RedirectResultTest.cs} (50%)
 create mode 100644 src/Http/Http.Results/test/RedirectToRouteResultTest.cs
 create mode 100644 src/Http/Http.Results/test/SignInResultTest.cs
 create mode 100644 src/Http/Http.Results/test/SignOutResultTest.cs
 create mode 100644 src/Http/Http.Results/test/StatusCodeResultTests.cs
 create mode 100644 src/Http/Http.Results/test/TestLinkGenerator.cs
 create mode 100644 src/Http/Http.Results/test/UnauthorizedResultTests.cs
 create mode 100644 src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
 create mode 100644 src/Http/Http.Results/test/UnprocessableEntityResultTests.cs
 create mode 100644 src/Http/Http.Results/test/VirtualFileResultTest.cs
 delete mode 100644 src/Mvc/Mvc.Core/test/FileContentActionResultTest.cs
 delete mode 100644 src/Mvc/Mvc.Core/test/FileContentResult.cs
 create mode 100644 src/Mvc/Mvc.Core/test/FileContentResultTest.cs
 rename src/Mvc/Mvc.Core/test/{FileResultTest.cs => FileResultHelperTest.cs} (97%)
 delete mode 100644 src/Mvc/Mvc.Core/test/PhysicalFileActionResultTest.cs
 delete mode 100644 src/Mvc/Mvc.Core/test/PhysicalFileResult.cs
 create mode 100644 src/Mvc/Mvc.Core/test/PhysicalFileResultTest.cs
 delete mode 100644 src/Mvc/Mvc.Core/test/VirtualFileActionResultTest .cs
 rename src/{Mvc/Mvc.Core/src/Formatters => Shared}/ResponseContentTypeHelper.cs (68%)
 create mode 100644 src/Shared/ResultsHelpers/FileResultHelper.cs
 create mode 100644 src/Shared/ResultsHelpers/FileResultInfo.cs
 create mode 100644 src/Shared/ResultsHelpers/FileResultLogging.cs
 create mode 100644 src/Shared/ResultsHelpers/SharedUrlHelper.cs
 rename src/{Mvc/Mvc.Core/test/BaseFileContentResultTest.cs => Shared/ResultsTests/FileContentResultTestBase.cs} (60%)
 create mode 100644 src/Shared/ResultsTests/FileStreamResultTestBase.cs
 rename src/{Mvc/Mvc.Core/test/BasePhysicalFileResultTest.cs => Shared/ResultsTests/PhysicalFileResultTestBase.cs} (58%)
 rename src/{Mvc/Mvc.Core/test/BaseRedirectResultTest.cs => Shared/ResultsTests/RedirectResultTestBase.cs} (50%)
 rename src/{Mvc/Mvc.Core/test/BaseVirtualFileResultTest.cs => Shared/ResultsTests/VirtualFileResultTestBase.cs} (50%)

diff --git a/AspNetCore.sln b/AspNetCore.sln
index 3caea6eb3817..cf184da11036 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1626,6 +1626,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Compon
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebSiteWithWebApplicationBuilderException", "src\Mvc\test\WebSites\SimpleWebSiteWithWebApplicationBuilderException\SimpleWebSiteWithWebApplicationBuilderException.csproj", "{5C641396-7E92-4F5C-A5A1-B4CDF480539B}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Results", "Http.Results", "{323C3EB6-1D15-4B3D-918D-699D7F64DED9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Results", "src\Http\Http.Results\src\Microsoft.AspNetCore.Http.Results.csproj", "{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Results.Tests", "src\Http\Http.Results\test\Microsoft.AspNetCore.Http.Results.Tests.csproj", "{F599EAA6-399F-4A91-9B1F-D311305B43D9}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -7745,6 +7751,30 @@ Global
 		{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x64.Build.0 = Release|Any CPU
 		{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.ActiveCfg = Release|Any CPU
 		{5C641396-7E92-4F5C-A5A1-B4CDF480539B}.Release|x86.Build.0 = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x64.Build.0 = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Debug|x86.Build.0 = Debug|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x64.ActiveCfg = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x64.Build.0 = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x86.ActiveCfg = Release|Any CPU
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8}.Release|x86.Build.0 = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x64.Build.0 = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Debug|x86.Build.0 = Debug|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x64.ActiveCfg = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x64.Build.0 = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x86.ActiveCfg = Release|Any CPU
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -8550,6 +8580,9 @@ Global
 		{558C46DE-DE16-41D5-8DB7-D6D748E32977} = {3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56}
 		{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD} = {44963D50-8B58-44E6-918D-788BCB406695}
 		{5C641396-7E92-4F5C-A5A1-B4CDF480539B} = {088C37A5-30D2-40FB-B031-D163CFBED006}
+		{323C3EB6-1D15-4B3D-918D-699D7F64DED9} = {627BE8B3-59E6-4F1D-8C9C-76B804D41724}
+		{092EA9F6-84D4-41EF-A618-BDA50A0E10A8} = {323C3EB6-1D15-4B3D-918D-699D7F64DED9}
+		{F599EAA6-399F-4A91-9B1F-D311305B43D9} = {323C3EB6-1D15-4B3D-918D-699D7F64DED9}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index fd90ae09e737..1035737d2d83 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -29,6 +29,7 @@
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Abstractions" ProjectPath="$(RepoRoot)src\Http\Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Extensions" ProjectPath="$(RepoRoot)src\Http\Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Features" ProjectPath="$(RepoRoot)src\Http\Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Results" ProjectPath="$(RepoRoot)src\Http\Http.Results\src\Microsoft.AspNetCore.Http.Results.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http" ProjectPath="$(RepoRoot)src\Http\Http\src\Microsoft.AspNetCore.Http.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Metadata" ProjectPath="$(RepoRoot)src\Http\Metadata\src\Microsoft.AspNetCore.Metadata.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Owin" ProjectPath="$(RepoRoot)src\Http\Owin\src\Microsoft.AspNetCore.Owin.csproj" />
diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props
index f230f2e82817..34a5b503e568 100644
--- a/eng/SharedFramework.Local.props
+++ b/eng/SharedFramework.Local.props
@@ -48,6 +48,7 @@
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Http.Abstractions" />
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Http.Extensions" />
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Http.Features" />
+    <AspNetCoreAppReference Include="Microsoft.AspNetCore.Http.Results" />
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Http" />
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Routing.Abstractions" />
     <AspNetCoreAppReference Include="Microsoft.AspNetCore.Routing" />
diff --git a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
new file mode 100644
index 000000000000..9d268a1fde93
--- /dev/null
+++ b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class AcceptedAtRouteResult : ObjectResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public AcceptedAtRouteResult(object? routeValues, object? value)
+            : this(routeName: null, routeValues: routeValues, value: value)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public AcceptedAtRouteResult(
+            string? routeName,
+            object? routeValues,
+            object? value)
+            : base(value, StatusCodes.Status202Accepted)
+        {
+            RouteName = routeName;
+            RouteValues = new RouteValueDictionary(routeValues);
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the route to use for generating the URL.
+        /// </summary>
+        public string? RouteName { get; }
+
+        /// <summary>
+        /// Gets or sets the route data to use for generating the URL.
+        /// </summary>
+        public RouteValueDictionary RouteValues { get; }
+
+        /// <inheritdoc />
+        protected override void OnFormatting(HttpContext context)
+        {
+            var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
+            var url = linkGenerator.GetUriByAddress(
+                context,
+                RouteName,
+                RouteValues,
+                fragment: FragmentString.Empty);
+
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new InvalidOperationException("No route matches the supplied values.");
+            }
+
+            context.Response.Headers.Location = url;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/AcceptedResult.cs b/src/Http/Http.Results/src/AcceptedResult.cs
new file mode 100644
index 000000000000..d7e1da54e514
--- /dev/null
+++ b/src/Http/Http.Results/src/AcceptedResult.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class AcceptedResult : ObjectResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+        /// provided.
+        /// </summary>
+        public AcceptedResult()
+            : base(value: null, StatusCodes.Status202Accepted)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="location">The location at which the status of requested content can be monitored.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public AcceptedResult(string? location, object? value)
+            : base(value, StatusCodes.Status202Accepted)
+        {
+            Location = location;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="locationUri">The location at which the status of requested content can be monitored.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public AcceptedResult(Uri locationUri, object? value)
+            : base(value, StatusCodes.Status202Accepted)
+        {
+            if (locationUri == null)
+            {
+                throw new ArgumentNullException(nameof(locationUri));
+            }
+
+            if (locationUri.IsAbsoluteUri)
+            {
+                Location = locationUri.AbsoluteUri;
+            }
+            else
+            {
+                Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the location at which the status of the requested content can be monitored.
+        /// </summary>
+        public string? Location { get; set; }
+
+        /// <inheritdoc />
+        protected override void OnFormatting(HttpContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            if (!string.IsNullOrEmpty(Location))
+            {
+                context.Response.Headers.Location = Location;
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/BadRequestObjectResult.cs b/src/Http/Http.Results/src/BadRequestObjectResult.cs
new file mode 100644
index 000000000000..2599ec549c6c
--- /dev/null
+++ b/src/Http/Http.Results/src/BadRequestObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class BadRequestObjectResult : ObjectResult
+    {
+        public BadRequestObjectResult(object? error)
+            : base(error, StatusCodes.Status400BadRequest)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/BadRequestResult.cs b/src/Http/Http.Results/src/BadRequestResult.cs
new file mode 100644
index 000000000000..eff601caa1f0
--- /dev/null
+++ b/src/Http/Http.Results/src/BadRequestResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class BadRequestResult : StatusCodeResult
+    {
+        public BadRequestResult() : base(StatusCodes.Status400BadRequest)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ChallengeResult.cs b/src/Http/Http.Results/src/ChallengeResult.cs
new file mode 100644
index 000000000000..7f4f16323140
--- /dev/null
+++ b/src/Http/Http.Results/src/ChallengeResult.cs
@@ -0,0 +1,120 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
+    /// </summary>
+    internal sealed partial class ChallengeResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/>.
+        /// </summary>
+        public ChallengeResult()
+            : this(Array.Empty<string>())
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+        /// specified authentication scheme.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
+        public ChallengeResult(string authenticationScheme)
+            : this(new[] { authenticationScheme })
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+        /// specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        public ChallengeResult(IList<string> authenticationSchemes)
+            : this(authenticationSchemes, properties: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+        /// specified <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ChallengeResult(AuthenticationProperties? properties)
+            : this(Array.Empty<string>(), properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+        /// specified authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ChallengeResult(string authenticationScheme, AuthenticationProperties? properties)
+            : this(new[] { authenticationScheme }, properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+        /// specified authentication schemes and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ChallengeResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+        {
+            AuthenticationSchemes = authenticationSchemes;
+            Properties = properties;
+        }
+
+        public IList<string> AuthenticationSchemes { get; init; } = Array.Empty<string>();
+
+        public AuthenticationProperties? Properties { get; init; }
+
+        public async Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<ChallengeResult>>();
+
+            Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
+
+            if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+            {
+                foreach (var scheme in AuthenticationSchemes)
+                {
+                    await httpContext.ChallengeAsync(scheme, Properties);
+                }
+            }
+            else
+            {
+                await httpContext.ChallengeAsync(Properties);
+            }
+        }
+
+        private static partial class Log
+        {
+            public static void ChallengeResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    ChallengeResultExecuting(logger, authenticationSchemes.ToArray());
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting")]
+            private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ConflictObjectResult.cs b/src/Http/Http.Results/src/ConflictObjectResult.cs
new file mode 100644
index 000000000000..7e5d8faed423
--- /dev/null
+++ b/src/Http/Http.Results/src/ConflictObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class ConflictObjectResult : ObjectResult
+    {
+        public ConflictObjectResult(object? error) :
+            base(error, StatusCodes.Status409Conflict)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ConflictResult.cs b/src/Http/Http.Results/src/ConflictResult.cs
new file mode 100644
index 000000000000..2baf7d079fb0
--- /dev/null
+++ b/src/Http/Http.Results/src/ConflictResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal class ConflictResult : StatusCodeResult
+    {
+        public ConflictResult() : base(StatusCodes.Status409Conflict)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ContentResult.cs b/src/Http/Http.Results/src/ContentResult.cs
new file mode 100644
index 000000000000..12f1ceb24f36
--- /dev/null
+++ b/src/Http/Http.Results/src/ContentResult.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed partial class ContentResult : IResult
+    {
+        private const string DefaultContentType = "text/plain; charset=utf-8";
+        private static readonly Encoding DefaultEncoding = Encoding.UTF8;
+
+        /// <summary>
+        /// Gets or set the content representing the body of the response.
+        /// </summary>
+        public string? Content { get; init; }
+
+        /// <summary>
+        /// Gets or sets the Content-Type header for the response.
+        /// </summary>
+        public string? ContentType { get; init; }
+
+        /// <summary>
+        /// Gets or sets the HTTP status code.
+        /// </summary>
+        public int? StatusCode { get; init; }
+
+        /// <summary>
+        /// Writes the content to the HTTP response.
+        /// </summary>
+        /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the asynchronous execute operation.</returns>
+        public async Task ExecuteAsync(HttpContext httpContext)
+        {
+            var response = httpContext.Response;
+
+            ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
+                ContentType,
+                response.ContentType,
+                (DefaultContentType, DefaultEncoding),
+                ResponseContentTypeHelper.GetEncoding,
+                out var resolvedContentType,
+                out var resolvedContentTypeEncoding);
+
+            response.ContentType = resolvedContentType;
+
+            if (StatusCode != null)
+            {
+                response.StatusCode = StatusCode.Value;
+            }
+
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<ContentResult>>();
+
+            Log.ContentResultExecuting(logger, resolvedContentType);
+
+            if (Content != null)
+            {
+                response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
+                await response.WriteAsync(Content, resolvedContentTypeEncoding);
+            }
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing ContentResult with HTTP Response ContentType of {ContentType}",
+                EventName = "ContentResultExecuting")]
+            internal static partial void ContentResultExecuting(ILogger logger, string contentType);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/CreatedAtRouteResult.cs b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
new file mode 100644
index 000000000000..8fe982930663
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class CreatedAtRouteResult : ObjectResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public CreatedAtRouteResult(object? routeValues, object? value)
+            : this(routeName: null, routeValues: routeValues, value: value)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public CreatedAtRouteResult(
+            string? routeName,
+            object? routeValues,
+            object? value)
+            : base(value, StatusCodes.Status201Created)
+        {
+            RouteName = routeName;
+            RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the route to use for generating the URL.
+        /// </summary>
+        public string? RouteName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the route data to use for generating the URL.
+        /// </summary>
+        public RouteValueDictionary? RouteValues { get; set; }
+
+        /// <inheritdoc />
+        protected override void OnFormatting(HttpContext context)
+        {
+            var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
+            var url = linkGenerator.GetUriByRouteValues(
+                context,
+                RouteName,
+                RouteValues,
+                fragment: FragmentString.Empty);
+
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new InvalidOperationException("No route matches the supplied values.");
+            }
+
+            context.Response.Headers.Location = url;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/CreatedResult.cs b/src/Http/Http.Results/src/CreatedResult.cs
new file mode 100644
index 000000000000..23be3d084722
--- /dev/null
+++ b/src/Http/Http.Results/src/CreatedResult.cs
@@ -0,0 +1,57 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class CreatedResult : ObjectResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="location">The location at which the content has been created.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public CreatedResult(string location, object? value)
+            : base(value, StatusCodes.Status201Created)
+        {
+            Location = location;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="location">The location at which the content has been created.</param>
+        /// <param name="value">The value to format in the entity body.</param>
+        public CreatedResult(Uri location, object? value)
+            : base(value, StatusCodes.Status201Created)
+        {
+            if (location == null)
+            {
+                throw new ArgumentNullException(nameof(location));
+            }
+
+            if (location.IsAbsoluteUri)
+            {
+                Location = location.AbsoluteUri;
+            }
+            else
+            {
+                Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the location at which the content has been created.
+        /// </summary>
+        public string Location { get; init; }
+
+        /// <inheritdoc />
+        protected override void OnFormatting(HttpContext context)
+        {
+            context.Response.Headers.Location = Location;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/FileContentResult.cs b/src/Http/Http.Results/src/FileContentResult.cs
new file mode 100644
index 000000000000..cc51ed505112
--- /dev/null
+++ b/src/Http/Http.Results/src/FileContentResult.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed partial class FileContentResult : FileResult, IResult
+    {
+        /// <summary>
+        /// Creates a new <see cref="FileContentResult"/> instance with
+        /// the provided <paramref name="fileContents"/> and the
+        /// provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileContents">The bytes that represent the file contents.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public FileContentResult(byte[] fileContents, string contentType)
+            : base(contentType)
+        {
+            FileContents = fileContents;
+        }
+
+        /// <summary>
+        /// Gets or sets the file contents.
+        /// </summary>
+        public byte[] FileContents { get; init; }
+
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileContentResult>>();
+            Log.ExecutingFileResult(logger, this);
+
+            var fileResultInfo = new FileResultInfo
+            {
+                ContentType = ContentType,
+                EnableRangeProcessing = EnableRangeProcessing,
+                EntityTag = EntityTag,
+                FileDownloadName = FileDownloadName,
+                LastModified = LastModified,
+            };
+
+            var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+                httpContext,
+                fileResultInfo,
+                FileContents.Length,
+                EnableRangeProcessing,
+                LastModified,
+                EntityTag,
+                logger);
+
+            if (!serveBody)
+            {
+                return Task.CompletedTask;
+            }
+
+            if (range != null && rangeLength == 0)
+            {
+                return Task.CompletedTask;
+            }
+
+            if (range != null)
+            {
+                FileResultHelper.Log.WritingRangeToBody(logger);
+            }
+
+            var fileContentStream = new MemoryStream(FileContents);
+            return FileResultHelper.WriteFileAsync(httpContext, fileContentStream, range, rangeLength);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/FileResult.cs b/src/Http/Http.Results/src/FileResult.cs
new file mode 100644
index 000000000000..1e0afe40a1f7
--- /dev/null
+++ b/src/Http/Http.Results/src/FileResult.cs
@@ -0,0 +1,93 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal abstract partial class FileResult
+    {
+        private string? _fileDownloadName;
+
+        /// <summary>
+        /// Creates a new <see cref="FileResult"/> instance with
+        /// the provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        protected FileResult(string contentType)
+        {
+            if (contentType == null)
+            {
+                throw new ArgumentNullException(nameof(contentType));
+            }
+
+            ContentType = contentType;
+        }
+
+        /// <summary>
+        /// Gets the Content-Type header for the response.
+        /// </summary>
+        public string ContentType { get; }
+
+        /// <summary>
+        /// Gets the file name that will be used in the Content-Disposition header of the response.
+        /// </summary>
+        [AllowNull]
+        public string FileDownloadName
+        {
+            get { return _fileDownloadName ?? string.Empty; }
+            init { _fileDownloadName = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the last modified information associated with the <see cref="FileResult"/>.
+        /// </summary>
+        public DateTimeOffset? LastModified { get; init; }
+
+        /// <summary>
+        /// Gets or sets the etag associated with the <see cref="FileResult"/>.
+        /// </summary>
+        public EntityTagHeaderValue? EntityTag { get; init; }
+
+        /// <summary>
+        /// Gets or sets the value that enables range processing for the <see cref="FileResult"/>.
+        /// </summary>
+        public bool EnableRangeProcessing { get; init; }
+
+        protected static partial class Log
+        {
+            public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    var fileResultType = fileResult.GetType().Name;
+                    ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
+                }
+            }
+
+            public static void ExecutingFileResult(ILogger logger, FileResult fileResult, string fileName)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    var fileResultType = fileResult.GetType().Name;
+                    ExecutingFileResult(logger, fileResultType, fileName, fileResult.FileDownloadName);
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing {FileResultType}, sending file with download name '{FileDownloadName}'.",
+                EventName = "ExecutingFileResultWithNoFileName",
+                SkipEnabledCheck = true)]
+            private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
+
+            [LoggerMessage(2, LogLevel.Information,
+                "Executing {FileResultType}, sending file '{FileDownloadPath}' with download name '{FileDownloadName}'.",
+                EventName = "ExecutingFileResult",
+                SkipEnabledCheck = true)]
+            private static partial void ExecutingFileResult(ILogger logger, string fileResultType, string fileDownloadPath, string fileDownloadName);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/FileStreamResult.cs b/src/Http/Http.Results/src/FileStreamResult.cs
new file mode 100644
index 000000000000..7b7862047ed1
--- /dev/null
+++ b/src/Http/Http.Results/src/FileStreamResult.cs
@@ -0,0 +1,110 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// Represents an <see cref="FileResult"/> that when executed will
+    /// write a file from a stream to the response.
+    /// </summary>
+    internal sealed class FileStreamResult : FileResult, IResult
+    {
+        /// <summary>
+        /// Creates a new <see cref="FileStreamResult"/> instance with
+        /// the provided <paramref name="fileStream"/> and the
+        /// provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileStream">The stream with the file.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public FileStreamResult(Stream fileStream, string contentType)
+            : this(fileStream, MediaTypeHeaderValue.Parse(contentType))
+        {
+            if (fileStream == null)
+            {
+                throw new ArgumentNullException(nameof(fileStream));
+            }
+
+            FileStream = fileStream;
+        }
+
+        /// <summary>
+        /// Creates a new <see cref="FileStreamResult"/> instance with
+        /// the provided <paramref name="fileStream"/> and the
+        /// provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileStream">The stream with the file.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public FileStreamResult(Stream fileStream, MediaTypeHeaderValue contentType)
+            : base(contentType.ToString())
+        {
+            if (fileStream == null)
+            {
+                throw new ArgumentNullException(nameof(fileStream));
+            }
+
+            FileStream = fileStream;
+        }
+
+        /// <summary>
+        /// Gets or sets the stream with the file that will be sent back as the response.
+        /// </summary>
+        public Stream FileStream { get; }
+
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileStreamResult>>();
+            using (FileStream)
+            {
+                Log.ExecutingFileResult(logger, this);
+
+                long? fileLength = null;
+                if (FileStream.CanSeek)
+                {
+                    fileLength = FileStream.Length;
+                }
+
+                var fileResultInfo = new FileResultInfo
+                {
+                    ContentType = ContentType,
+                    EnableRangeProcessing = EnableRangeProcessing,
+                    EntityTag = EntityTag,
+                    FileDownloadName = FileDownloadName,
+                };
+
+                var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+                    httpContext,
+                    fileResultInfo,
+                    fileLength,
+                    EnableRangeProcessing,
+                    LastModified,
+                    EntityTag,
+                    logger);
+
+                if (!serveBody)
+                {
+                    return Task.CompletedTask;
+                }
+
+                if (range != null && rangeLength == 0)
+                {
+                    return Task.CompletedTask;
+                }
+
+                if (range != null)
+                {
+                    FileResultHelper.Log.WritingRangeToBody(logger);
+                }
+
+                return FileResultHelper.WriteFileAsync(httpContext, FileStream, range, rangeLength);
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ForbidResult.cs b/src/Http/Http.Results/src/ForbidResult.cs
new file mode 100644
index 000000000000..9a22b6240d3b
--- /dev/null
+++ b/src/Http/Http.Results/src/ForbidResult.cs
@@ -0,0 +1,125 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed partial class ForbidResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/>.
+        /// </summary>
+        public ForbidResult()
+            : this(Array.Empty<string>())
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/> with the
+        /// specified authentication scheme.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
+        public ForbidResult(string authenticationScheme)
+            : this(new[] { authenticationScheme })
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/> with the
+        /// specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        public ForbidResult(IList<string> authenticationSchemes)
+            : this(authenticationSchemes, properties: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/> with the
+        /// specified <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ForbidResult(AuthenticationProperties? properties)
+            : this(Array.Empty<string>(), properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/> with the
+        /// specified authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ForbidResult(string authenticationScheme, AuthenticationProperties? properties)
+            : this(new[] { authenticationScheme }, properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ForbidResult"/> with the
+        /// specified authentication schemes and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        public ForbidResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+        {
+            AuthenticationSchemes = authenticationSchemes;
+            Properties = properties;
+        }
+
+        /// <summary>
+        /// Gets or sets the authentication schemes that are challenged.
+        /// </summary>
+        public IList<string> AuthenticationSchemes { get; init; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the authentication challenge.
+        /// </summary>
+        public AuthenticationProperties? Properties { get; init; }
+
+        /// <inheritdoc />
+        public async Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<ForbidResult>>();
+
+            Log.ForbidResultExecuting(logger, AuthenticationSchemes);
+
+            if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+            {
+                for (var i = 0; i < AuthenticationSchemes.Count; i++)
+                {
+                    await httpContext.ForbidAsync(AuthenticationSchemes[i], Properties);
+                }
+            }
+            else
+            {
+                await httpContext.ForbidAsync(Properties);
+            }
+        }
+
+        private static partial class Log
+        {
+            public static void ForbidResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    ForbidResultExecuting(logger, authenticationSchemes.ToArray());
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
+            private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
+        }
+
+    }
+}
diff --git a/src/Http/Http.Results/src/JsonResult.cs b/src/Http/Http.Results/src/JsonResult.cs
new file mode 100644
index 000000000000..597048c2b627
--- /dev/null
+++ b/src/Http/Http.Results/src/JsonResult.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An action result which formats the given object as JSON.
+    /// </summary>
+    internal sealed partial class JsonResult : IResult
+    {
+        /// <summary>
+        /// Gets or sets the <see cref="Net.Http.Headers.MediaTypeHeaderValue"/> representing the Content-Type header of the response.
+        /// </summary>
+        public string? ContentType { get; init; }
+
+        /// <summary>
+        /// Gets or sets the serializer settings.
+        /// <para>
+        /// When using <c>System.Text.Json</c>, this should be an instance of <see cref="JsonSerializerOptions" />
+        /// </para>
+        /// <para>
+        /// When using <c>Newtonsoft.Json</c>, this should be an instance of <c>JsonSerializerSettings</c>.
+        /// </para>
+        /// </summary>
+        public JsonSerializerOptions? JsonSerializerOptions { get; init; }
+
+        /// <summary>
+        /// Gets or sets the HTTP status code.
+        /// </summary>
+        public int? StatusCode { get; init; }
+
+        /// <summary>
+        /// Gets or sets the value to be formatted.
+        /// </summary>
+        public object? Value { get; init; }
+
+        /// <summary>
+        /// Write the result as JSON to the HTTP response.
+        /// </summary>
+        /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the asynchronous execute operation.</returns>
+        Task IResult.ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<JsonResult>>();
+            Log.JsonResultExecuting(logger, Value);
+
+            if (StatusCode is int statusCode)
+            {
+                httpContext.Response.StatusCode = statusCode;
+            }
+
+            return httpContext.Response.WriteAsJsonAsync(Value, JsonSerializerOptions, ContentType);
+        }
+
+        private static partial class Log
+        {
+            public static void JsonResultExecuting(ILogger logger, object? value)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    var type = value == null ? "null" : value.GetType().FullName!;
+                    JsonResultExecuting(logger, type);
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing JsonResult, writing value of type '{Type}'.",
+                EventName = "JsonResultExecuting",
+                SkipEnabledCheck = true)]
+            private static partial void JsonResultExecuting(ILogger logger, string type);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/LocalRedirectResult.cs b/src/Http/Http.Results/src/LocalRedirectResult.cs
new file mode 100644
index 000000000000..51aa27568b2e
--- /dev/null
+++ b/src/Http/Http.Results/src/LocalRedirectResult.cs
@@ -0,0 +1,111 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
+    /// or Permanent Redirect (308) response with a Location header to the supplied local URL.
+    /// </summary>
+    internal sealed partial class LocalRedirectResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        public LocalRedirectResult(string localUrl)
+             : this(localUrl, permanent: false)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+        public LocalRedirectResult(string localUrl, bool permanent)
+            : this(localUrl, permanent, preserveMethod: false)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+        /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request's method.</param>
+        public LocalRedirectResult(string localUrl, bool permanent, bool preserveMethod)
+        {
+            if (string.IsNullOrEmpty(localUrl))
+            {
+                throw new ArgumentException("Argument cannot be null or empty", nameof(localUrl));
+            }
+
+            Permanent = permanent;
+            PreserveMethod = preserveMethod;
+            Url = localUrl;
+        }
+
+        /// <summary>
+        /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
+        /// </summary>
+        public bool Permanent { get; }
+
+        /// <summary>
+        /// Gets or sets an indication that the redirect preserves the initial request method.
+        /// </summary>
+        public bool PreserveMethod { get; }
+
+        /// <summary>
+        /// Gets or sets the local URL to redirect to.
+        /// </summary>
+        public string Url { get; }
+
+        /// <inheritdoc />
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            if (!SharedUrlHelper.IsLocalUrl(Url))
+            {
+                throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
+            }
+
+            var destinationUrl = SharedUrlHelper.Content(httpContext, Url);
+
+            // IsLocalUrl is called to handle URLs starting with '~/'.
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<LocalRedirectResult>>();
+
+            Log.LocalRedirectResultExecuting(logger, destinationUrl);
+
+            if (PreserveMethod)
+            {
+                httpContext.Response.StatusCode = Permanent
+                    ? StatusCodes.Status308PermanentRedirect
+                    : StatusCodes.Status307TemporaryRedirect;
+                httpContext.Response.Headers.Location = destinationUrl;
+            }
+            else
+            {
+                httpContext.Response.Redirect(destinationUrl, Permanent);
+            }
+
+            return Task.CompletedTask;
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing LocalRedirectResult, redirecting to {Destination}.",
+                EventName = "LocalRedirectResultExecuting")]
+            public static partial void LocalRedirectResultExecuting(ILogger logger, string destination);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj
new file mode 100644
index 000000000000..54b85a37bbfb
--- /dev/null
+++ b/src/Http/Http.Results/src/Microsoft.AspNetCore.Http.Results.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core types that implement Microsoft.AspNetCore.Http.IResult.</Description>
+    <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+    <IsAspNetCoreApp>true</IsAspNetCoreApp>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+    <IsPackable>false</IsPackable>
+    <Nullable>enable</Nullable>
+    <RootNamespace>Microsoft.AspNetCore.Http.Result</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" />
+    <Compile Include="$(SharedSourceRoot)ResponseContentTypeHelper.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)ResultsHelpers\*.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)RangeHelper\RangeHelper.cs" LinkBase="Shared" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Authentication.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+    <Reference Include="Microsoft.AspNetCore.Routing" />
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
+
+    <InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Results.Tests" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Results/src/NoContentResult.cs b/src/Http/Http.Results/src/NoContentResult.cs
new file mode 100644
index 000000000000..120ce546ef09
--- /dev/null
+++ b/src/Http/Http.Results/src/NoContentResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal class NoContentResult : StatusCodeResult
+    {
+        public NoContentResult() : base(StatusCodes.Status204NoContent)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/NotFoundObjectResult.cs b/src/Http/Http.Results/src/NotFoundObjectResult.cs
new file mode 100644
index 000000000000..fbda773257c0
--- /dev/null
+++ b/src/Http/Http.Results/src/NotFoundObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class NotFoundObjectResult : ObjectResult
+    {
+        public NotFoundObjectResult(object? value)
+            : base(value, StatusCodes.Status404NotFound)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/NotFoundResult.cs b/src/Http/Http.Results/src/NotFoundResult.cs
new file mode 100644
index 000000000000..b0d7902eb8e4
--- /dev/null
+++ b/src/Http/Http.Results/src/NotFoundResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class NotFoundResult : StatusCodeResult
+    {
+        public NotFoundResult() : base(StatusCodes.Status404NotFound)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/ObjectResult.cs b/src/Http/Http.Results/src/ObjectResult.cs
new file mode 100644
index 000000000000..679743da3684
--- /dev/null
+++ b/src/Http/Http.Results/src/ObjectResult.cs
@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal partial class ObjectResult : IResult
+    {
+        /// <summary>
+        /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>.
+        /// </summary>
+        public ObjectResult(object? value, int statusCode)
+        {
+            Value = value;
+            StatusCode = statusCode;
+        }
+
+        /// <summary>
+        /// The object result.
+        /// </summary>
+        public object? Value { get; }
+
+        /// <summary>
+        /// Gets or sets the HTTP status code.
+        /// </summary>
+        public int? StatusCode { get; }
+
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
+            var logger = loggerFactory.CreateLogger(GetType());
+            Log.ObjectResultExecuting(logger, Value);
+
+            if (StatusCode is int statusCode)
+            {
+                httpContext.Response.StatusCode = statusCode;
+            }
+
+            OnFormatting(httpContext);
+            return httpContext.Response.WriteAsJsonAsync(Value);
+        }
+
+        protected virtual void OnFormatting(HttpContext httpContext)
+        {
+        }
+
+        private static partial class Log
+        {
+            public static void ObjectResultExecuting(ILogger logger, object? value)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    var valueType = value is null ? "null" : value.GetType().FullName!;
+                    ObjectResultExecuting(logger, valueType);
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information, "Writing value of type '{Type}'.", EventName = "ObjectResultExecuting", SkipEnabledCheck = true)]
+            public static partial void ObjectResultExecuting(ILogger logger, string type);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/OkObjectResult.cs b/src/Http/Http.Results/src/OkObjectResult.cs
new file mode 100644
index 000000000000..f7ec6e9603bc
--- /dev/null
+++ b/src/Http/Http.Results/src/OkObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class OkObjectResult : ObjectResult
+    {
+        public OkObjectResult(object? value)
+            : base(value, StatusCodes.Status200OK)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/OkResult.cs b/src/Http/Http.Results/src/OkResult.cs
new file mode 100644
index 000000000000..9cf0430fa990
--- /dev/null
+++ b/src/Http/Http.Results/src/OkResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal class OkResult : StatusCodeResult
+    {
+        public OkResult() : base(StatusCodes.Status200OK)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/PhyiscalFileResult.cs b/src/Http/Http.Results/src/PhyiscalFileResult.cs
new file mode 100644
index 000000000000..ed4bed6aa477
--- /dev/null
+++ b/src/Http/Http.Results/src/PhyiscalFileResult.cs
@@ -0,0 +1,123 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// A <see cref="PhysicalFileResult"/> on execution will write a file from disk to the response
+    /// using mechanisms provided by the host.
+    /// </summary>
+    internal sealed partial class PhysicalFileResult : FileResult, IResult
+    {
+        /// <summary>
+        /// Creates a new <see cref="PhysicalFileResult"/> instance with
+        /// the provided <paramref name="fileName"/> and the provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileName">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public PhysicalFileResult(string fileName, string contentType)
+            : base(contentType)
+        {
+            FileName = fileName;
+        }
+
+        /// <summary>
+        /// Gets or sets the path to the file that will be sent back as the response.
+        /// </summary>
+        public string FileName { get; }
+
+        // For testing
+        public Func<string, FileInfoWrapper> GetFileInfoWrapper { get; init; } =
+            static path => new FileInfoWrapper(path);
+
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var fileInfo = GetFileInfoWrapper(FileName);
+            if (!fileInfo.Exists)
+            {
+                throw new FileNotFoundException($"Could not find file: {FileName}");
+            }
+
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<PhysicalFileResult>>();
+
+            Log.ExecutingFileResult(logger, this, FileName);
+
+            var lastModified = LastModified ?? fileInfo.LastWriteTimeUtc;
+            var fileResultInfo = new FileResultInfo
+            {
+                ContentType = ContentType,
+                EnableRangeProcessing = EnableRangeProcessing,
+                EntityTag = EntityTag,
+                FileDownloadName = FileDownloadName,
+                LastModified = lastModified,
+            };
+
+            var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+                httpContext,
+                fileResultInfo,
+                fileInfo.Length,
+                EnableRangeProcessing,
+                lastModified,
+                EntityTag,
+                logger);
+
+            if (!serveBody)
+            {
+                return Task.CompletedTask;
+            }
+
+            if (range != null && rangeLength == 0)
+            {
+                return Task.CompletedTask;
+            }
+
+            var response = httpContext.Response;
+            if (!Path.IsPathRooted(FileName))
+            {
+                throw new NotSupportedException($"Path '{FileName}' was not rooted.");
+            }
+
+            if (range != null)
+            {
+                FileResultHelper.Log.WritingRangeToBody(logger);
+            }
+
+            var offset = 0L;
+            var count = (long?)null;
+            if (range != null)
+            {
+                offset = range.From ?? 0L;
+                count = rangeLength;
+            }
+
+            return response.SendFileAsync(
+                FileName,
+                offset: offset,
+                count: count);
+        }
+
+        internal readonly struct FileInfoWrapper
+        {
+            public FileInfoWrapper(string path)
+            {
+                var fileInfo = new FileInfo(path);
+                Exists = fileInfo.Exists;
+                Length = fileInfo.Length;
+                LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
+            }
+
+            public bool Exists { get; init; }
+
+            public long Length { get; init; }
+
+            public DateTimeOffset LastWriteTimeUtc { get; init; }
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/PublicAPI.Shipped.txt b/src/Http/Http.Results/src/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/Http/Http.Results/src/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..dffa3a246402
--- /dev/null
+++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt
@@ -0,0 +1,104 @@
+#nullable enable
+Microsoft.AspNetCore.Http.Results
+static Microsoft.AspNetCore.Http.Results.Accepted() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(System.Uri! uri) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(string? uri) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Accepted(string? uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute(string? routeName, object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.BadRequest() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.BadRequest(object? error) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Challenge(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Conflict() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Conflict(object? error) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, Microsoft.Net.Http.Headers.MediaTypeHeaderValue? contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Content(string! content, string! contentType, System.Text.Encoding! contentEncoding) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Created(System.Uri! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Created(string! uri, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, object? routeValues, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.CreatedAtRoute(string? routeName, object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(System.IO.Stream! fileStream, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(byte[]! fileContents, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.File(string! virtualPath, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Forbid(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Json(object? data, System.Text.Json.JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirect(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPermanent(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPermanentPreserveMethod(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.LocalRedirectPreserveMethod(string! localUrl) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NoContent() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NotFound() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.NotFound(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Ok() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Ok(object? value) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, System.DateTimeOffset? lastModified, Microsoft.Net.Http.Headers.EntityTagHeaderValue! entityTag, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.PhysicalFile(string! physicalPath, string! contentType, string? fileDownloadName, bool enableRangeProcessing) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Redirect(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPermanent(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPermanentPreserveMethod(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectPreserveMethod(string! url) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, object? routeValues, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoute(string? routeName, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, object? routeValues) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, object? routeValues, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanent(string? routeName, string? fragment) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePermanentPreserveMethod(string? routeName = null, object? routeValues = null, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.RedirectToRoutePreserveMethod(string? routeName = null, object? routeValues = null, string? fragment = null) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, string! authenticationScheme) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignIn(System.Security.Claims.ClaimsPrincipal! principal, string! authenticationScheme) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(Microsoft.AspNetCore.Authentication.AuthenticationProperties! properties, params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.SignOut(params string![]! authenticationSchemes) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.StatusCode(int statusCode) -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.Unauthorized() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.UnprocessableEntity() -> Microsoft.AspNetCore.Http.IResult!
+static Microsoft.AspNetCore.Http.Results.UnprocessableEntity(object? error) -> Microsoft.AspNetCore.Http.IResult!
diff --git a/src/Http/Http.Results/src/RedirectResult.cs b/src/Http/Http.Results/src/RedirectResult.cs
new file mode 100644
index 000000000000..8624683290b6
--- /dev/null
+++ b/src/Http/Http.Results/src/RedirectResult.cs
@@ -0,0 +1,107 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed partial class RedirectResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="url">The local URL to redirect to.</param>
+        public RedirectResult(string url)
+            : this(url, permanent: false)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+        public RedirectResult(string url, bool permanent)
+            : this(url, permanent, preserveMethod: false)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectResult"/> class with the values
+        /// provided.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+        /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+        public RedirectResult(string url, bool permanent, bool preserveMethod)
+        {
+            if (url == null)
+            {
+                throw new ArgumentNullException(nameof(url));
+            }
+
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new ArgumentException("Argument cannot be null or empty", nameof(url));
+            }
+
+            Permanent = permanent;
+            PreserveMethod = preserveMethod;
+            Url = url;
+        }
+
+        /// <summary>
+        /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
+        /// </summary>
+        public bool Permanent { get; }
+
+        /// <summary>
+        /// Gets or sets an indication that the redirect preserves the initial request method.
+        /// </summary>
+        public bool PreserveMethod { get; }
+
+        /// <summary>
+        /// Gets or sets the URL to redirect to.
+        /// </summary>
+        public string Url { get; }
+
+        /// <inheritdoc />
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectResult>>();
+
+            // IsLocalUrl is called to handle URLs starting with '~/'.
+            var destinationUrl = SharedUrlHelper.IsLocalUrl(Url) ? SharedUrlHelper.Content(httpContext, Url) : Url;
+
+            Log.RedirectResultExecuting(logger, destinationUrl);
+
+            if (PreserveMethod)
+            {
+                httpContext.Response.StatusCode = Permanent
+                    ? StatusCodes.Status308PermanentRedirect
+                    : StatusCodes.Status307TemporaryRedirect;
+                httpContext.Response.Headers.Location = destinationUrl;
+            }
+            else
+            {
+                httpContext.Response.Redirect(destinationUrl, Permanent);
+            }
+
+            return Task.CompletedTask;
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing RedirectResult, redirecting to {Destination}.",
+                EventName = "RedirectResultExecuting")]
+            public static partial void RedirectResultExecuting(ILogger logger, string destination);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/RedirectToRouteResult.cs b/src/Http/Http.Results/src/RedirectToRouteResult.cs
new file mode 100644
index 000000000000..11711b1d8cb0
--- /dev/null
+++ b/src/Http/Http.Results/src/RedirectToRouteResult.cs
@@ -0,0 +1,194 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
+    /// or Permanent Redirect (308) response with a Location header.
+    /// Targets a registered route.
+    /// </summary>
+    internal sealed partial class RedirectToRouteResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeValues">The parameters for the route.</param>
+        public RedirectToRouteResult(object? routeValues)
+            : this(routeName: null, routeValues: routeValues)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues)
+            : this(routeName, routeValues, permanent: false)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues,
+            bool permanent)
+            : this(routeName, routeValues, permanent, fragment: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+        /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues,
+            bool permanent,
+            bool preserveMethod)
+            : this(routeName, routeValues, permanent, preserveMethod, fragment: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues,
+            string? fragment)
+            : this(routeName, routeValues, permanent: false, fragment: fragment)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues,
+            bool permanent,
+            string? fragment)
+            : this(routeName, routeValues, permanent, preserveMethod: false, fragment: fragment)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+        /// provided.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for the route.</param>
+        /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+        /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        public RedirectToRouteResult(
+            string? routeName,
+            object? routeValues,
+            bool permanent,
+            bool preserveMethod,
+            string? fragment)
+        {
+            RouteName = routeName;
+            RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
+            PreserveMethod = preserveMethod;
+            Permanent = permanent;
+            Fragment = fragment;
+        }
+
+        /// <summary>
+        /// Gets or sets the name of the route to use for generating the URL.
+        /// </summary>
+        public string? RouteName { get; }
+
+        /// <summary>
+        /// Gets or sets the route data to use for generating the URL.
+        /// </summary>
+        public RouteValueDictionary? RouteValues { get; }
+
+        /// <summary>
+        /// Gets or sets an indication that the redirect is permanent.
+        /// </summary>
+        public bool Permanent { get; }
+
+        /// <summary>
+        /// Gets or sets an indication that the redirect preserves the initial request method.
+        /// </summary>
+        public bool PreserveMethod { get; }
+
+        /// <summary>
+        /// Gets or sets the fragment to add to the URL.
+        /// </summary>
+        public string? Fragment { get; }
+
+        /// <inheritdoc />
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
+
+            var destinationUrl = linkGenerator.GetUriByRouteValues(
+                httpContext,
+                RouteName,
+                RouteValues,
+                fragment: Fragment == null ? FragmentString.Empty : new FragmentString("#" + Fragment));
+            if (string.IsNullOrEmpty(destinationUrl))
+            {
+                throw new InvalidOperationException("No route matches the supplied values.");
+            }
+
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectToRouteResult>>();
+            Log.RedirectToRouteResultExecuting(logger, destinationUrl, RouteName);
+
+            if (PreserveMethod)
+            {
+                httpContext.Response.StatusCode = Permanent ?
+                    StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect;
+                httpContext.Response.Headers.Location = destinationUrl;
+            }
+            else
+            {
+                httpContext.Response.Redirect(destinationUrl, Permanent);
+            }
+
+            return Task.CompletedTask;
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing RedirectToRouteResult, redirecting to {Destination} from route {RouteName}.",
+                EventName = "RedirectToRouteResultExecuting")]
+            public static partial void RedirectToRouteResultExecuting(ILogger logger, string destination, string? routeName);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs
new file mode 100644
index 000000000000..467d5c4afab1
--- /dev/null
+++ b/src/Http/Http.Results/src/Results.cs
@@ -0,0 +1,1465 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Json;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http.Result;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    ///
+    /// </summary>
+    public static class Results
+    {
+        #region SignIn / SignOut / Challenge
+
+        /// <summary>
+        /// Creates a <see cref="ChallengeResult"/>.
+        /// </summary>
+        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
+        /// <remarks>
+        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+        /// are among likely status results.
+        /// </remarks>
+        public static IResult Challenge()
+            => new ChallengeResult();
+
+        /// <summary>
+        /// Creates a <see cref="ChallengeResult"/> with the specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
+        /// <remarks>
+        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+        /// are among likely status results.
+        /// </remarks>
+        public static IResult Challenge(params string[] authenticationSchemes)
+            => new ChallengeResult { AuthenticationSchemes = authenticationSchemes };
+
+        /// <summary>
+        /// Creates a <see cref="ChallengeResult"/> with the specified <paramref name="properties" />.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
+        /// <remarks>
+        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+        /// are among likely status results.
+        /// </remarks>
+        public static IResult Challenge(AuthenticationProperties properties)
+            => new ChallengeResult { Properties = properties };
+
+        /// <summary>
+        /// Creates a <see cref="ChallengeResult"/> with the specified authentication schemes and
+        /// <paramref name="properties" />.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
+        /// <remarks>
+        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+        /// are among likely status results.
+        /// </remarks>
+        public static IResult Challenge(
+            AuthenticationProperties properties,
+            params string[] authenticationSchemes)
+            => new ChallengeResult { AuthenticationSchemes = authenticationSchemes, Properties = properties };
+
+        /// <summary>
+        /// Creates a <see cref="SignInResult"/>.
+        /// </summary>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
+        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        public static IResult SignIn(ClaimsPrincipal principal)
+            => new SignInResult(principal);
+
+        /// <summary>
+        /// Creates a <see cref="SignInResult"/> with the specified authentication scheme.
+        /// </summary>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
+        /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
+        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        public static IResult SignIn(ClaimsPrincipal principal, string authenticationScheme)
+            => new SignInResult(authenticationScheme, principal);
+
+        /// <summary>
+        /// Creates a <see cref="SignInResult"/> with <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        public static IResult SignIn(
+            ClaimsPrincipal principal,
+            AuthenticationProperties properties)
+            => new SignInResult(principal, properties);
+
+        /// <summary>
+        /// Creates a <see cref="SignInResult"/> with the specified authentication scheme and
+        /// <paramref name="properties" />.
+        /// </summary>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+        /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
+        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        public static IResult SignIn(
+            ClaimsPrincipal principal,
+            AuthenticationProperties properties,
+            string authenticationScheme)
+            => new SignInResult(authenticationScheme, principal, properties);
+
+        /// <summary>
+        /// Creates a <see cref="SignOutResult"/>.
+        /// </summary>
+        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        public static IResult SignOut()
+            => new SignOutResult();
+
+        /// <summary>
+        /// Creates a <see cref="SignOutResult"/> with <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        public static IResult SignOut(AuthenticationProperties properties)
+            => new SignOutResult(properties);
+
+        /// <summary>
+        /// Creates a <see cref="SignOutResult"/> with the specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to use for the sign-out operation.</param>
+        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        public static IResult SignOut(params string[] authenticationSchemes)
+            => new SignOutResult(authenticationSchemes);
+
+        /// <summary>
+        /// Creates a <see cref="SignOutResult"/> with the specified authentication schemes and
+        /// <paramref name="properties" />.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+        /// <param name="authenticationSchemes">The authentication scheme to use for the sign-out operation.</param>
+        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        public static IResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes)
+            => new SignOutResult(authenticationSchemes, properties);
+        #endregion
+
+        #region ContentResult
+        /// <summary>
+        /// Creates a <see cref="IResult"/> object by specifying a <paramref name="content"/> string.
+        /// </summary>
+        /// <param name="content">The content to write to the response.</param>
+        /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+        public static IResult Content(string content)
+            => Content(content, (MediaTypeHeaderValue?)null);
+
+        /// <summary>
+        /// Creates a <see cref="ContentResult"/> object by specifying a
+        /// <paramref name="content"/> string and a content type.
+        /// </summary>
+        /// <param name="content">The content to write to the response.</param>
+        /// <param name="contentType">The content type (MIME type).</param>
+        /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+        public static IResult Content(string content, string contentType)
+            => Content(content, MediaTypeHeaderValue.Parse(contentType));
+
+        /// <summary>
+        /// Creates a <see cref="IResult"/> object by specifying a
+        /// <paramref name="content"/> string, a <paramref name="contentType"/>, and <paramref name="contentEncoding"/>.
+        /// </summary>
+        /// <param name="content">The content to write to the response.</param>
+        /// <param name="contentType">The content type (MIME type).</param>
+        /// <param name="contentEncoding">The content encoding.</param>
+        /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+        /// <remarks>
+        /// If encoding is provided by both the 'charset' and the <paramref name="contentEncoding"/> parameters, then
+        /// the <paramref name="contentEncoding"/> parameter is chosen as the final encoding.
+        /// </remarks>
+        public static IResult Content(string content, string contentType, Encoding contentEncoding)
+        {
+            var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType);
+            mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding;
+            return Content(content, mediaTypeHeaderValue);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="IResult"/> object by specifying a
+        /// <paramref name="content"/> string and a <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="content">The content to write to the response.</param>
+        /// <param name="contentType">The content type (MIME type).</param>
+        /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+        public static IResult Content(string content, MediaTypeHeaderValue? contentType)
+        {
+            return new ContentResult
+            {
+                Content = content,
+                ContentType = contentType?.ToString()
+            };
+        }
+        #endregion
+
+        #region ForbidResult
+        /// <summary>
+        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default).
+        /// </summary>
+        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <remarks>
+        /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
+        /// a redirect to show a login page.
+        /// </remarks>
+        public static IResult Forbid()
+            => new ForbidResult();
+
+        /// <summary>
+        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
+        /// specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <remarks>
+        /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
+        /// a redirect to show a login page.
+        /// </remarks>
+        public static IResult Forbid(params string[] authenticationSchemes)
+            => new ForbidResult { AuthenticationSchemes = authenticationSchemes };
+
+        /// <summary>
+        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
+        /// specified <paramref name="properties" />.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <remarks>
+        /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
+        /// a redirect to show a login page.
+        /// </remarks>
+        public static IResult Forbid(AuthenticationProperties properties)
+            => new ForbidResult { Properties = properties };
+
+        /// <summary>
+        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
+        /// specified authentication schemes and <paramref name="properties" />.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <remarks>
+        /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
+        /// a redirect to show a login page.
+        /// </remarks>
+        public static IResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes)
+            => new ForbidResult { Properties = properties, AuthenticationSchemes = authenticationSchemes, };
+        #endregion
+
+        /// <summary>
+        /// Creates a <see cref="JsonResult"/> object that serializes the specified <paramref name="data"/> object
+        /// to JSON.
+        /// </summary>
+        /// <param name="data">The object to write as JSON.</param>
+        /// <param name="options">The serializer options use when serializing the value.</param>
+        /// <param name="contentType">The content-type to set on the response.</param>
+        /// <param name="statusCode">The status code to set on the response.</param>
+        /// <returns>The created <see cref="JsonResult"/> that serializes the specified <paramref name="data"/>
+        /// as JSON format for the response.</returns>
+        /// <remarks>Callers should cache an instance of serializer settings to avoid
+        /// recreating cached data with each call.</remarks>
+        public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
+        {
+            return new JsonResult
+            {
+                Value = data,
+                JsonSerializerOptions = options,
+                ContentType = contentType,
+                StatusCode = statusCode,
+            };
+        }
+
+        #region FileContentResult
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType)
+            => File(fileContents, contentType, fileDownloadName: null);
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, bool enableRangeProcessing)
+            => File(fileContents, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName)
+            => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName };
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+            => new FileContentResult(fileContents, contentType)
+            {
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new FileContentResult(fileContents, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new FileContentResult(fileContents, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new FileContentResult(fileContents, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileContents">The file contents.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new FileContentResult(fileContents, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+        #endregion
+
+        #region FileStreamResult
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType)
+            => File(fileStream, contentType, fileDownloadName: null);
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, bool enableRangeProcessing)
+            => File(fileStream, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, string? fileDownloadName)
+            => new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName };
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+            => new FileStreamResult(fileStream, contentType)
+            {
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new FileStreamResult(fileStream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>),
+        /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new FileStreamResult(fileStream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new FileStreamResult(fileStream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+            };
+        }
+
+        /// <summary>
+        /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <remarks>
+        /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+        /// </remarks>
+        public static IResult File(Stream fileStream, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new FileStreamResult(fileStream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+        #endregion
+
+        #region PhysicalFileResult
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType)
+            => PhysicalFile(physicalPath, contentType, fileDownloadName: null);
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType, bool enableRangeProcessing)
+            => PhysicalFile(physicalPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(
+            string physicalPath,
+            string contentType,
+            string? fileDownloadName)
+            => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName };
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(
+            string physicalPath,
+            string contentType,
+            string? fileDownloadName,
+            bool enableRangeProcessing)
+            => new PhysicalFileResult(physicalPath, contentType)
+            {
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), and
+        /// the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new PhysicalFileResult(physicalPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), and
+        /// the specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new PhysicalFileResult(physicalPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new PhysicalFileResult(physicalPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), the
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new PhysicalFileResult(physicalPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+        #endregion
+
+        #region VirtualFileResult
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType)
+            => File(virtualPath, contentType, fileDownloadName: null);
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, bool enableRangeProcessing)
+            => File(virtualPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, string? fileDownloadName)
+            => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName };
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
+        /// specified <paramref name="contentType" /> as the Content-Type and the
+        /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, string? fileDownloadName, bool enableRangeProcessing)
+            => new VirtualFileResult(virtualPath, contentType)
+            {
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), and the 
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new VirtualFileResult(virtualPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), and the 
+        /// specified <paramref name="contentType" /> as the Content-Type.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new VirtualFileResult(virtualPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), the 
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
+        {
+            return new VirtualFileResult(virtualPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+            };
+        }
+
+        /// <summary>
+        /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), the 
+        /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+        /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </summary>
+        /// <param name="virtualPath">The virtual path of the file to be returned.</param>
+        /// <param name="contentType">The Content-Type of the file.</param>
+        /// <param name="fileDownloadName">The suggested file name.</param>
+        /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+        /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+        /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
+        {
+            return new VirtualFileResult(virtualPath, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                FileDownloadName = fileDownloadName,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+        }
+        #endregion
+
+        #region RedirectResult variants
+        /// <summary>
+        /// Creates a <see cref="RedirectResult"/> object that redirects (<see cref="StatusCodes.Status302Found"/>)
+        /// to the specified <paramref name="url"/>.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        public static IResult Redirect(string url)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+            }
+
+            return new RedirectResult(url);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to true
+        /// (<see cref="StatusCodes.Status301MovedPermanently"/>) using the specified <paramref name="url"/>.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        public static IResult RedirectPermanent(string url)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+            }
+
+            return new RedirectResult(url, permanent: true);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to false
+        /// and <see cref="RedirectResult.PreserveMethod"/> set to true (<see cref="StatusCodes.Status307TemporaryRedirect"/>) 
+        /// using the specified <paramref name="url"/>.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        public static IResult RedirectPreserveMethod(string url)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+            }
+
+            return new RedirectResult(url: url, permanent: false, preserveMethod: true);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to true
+        /// and <see cref="RedirectResult.PreserveMethod"/> set to true (<see cref="StatusCodes.Status308PermanentRedirect"/>) 
+        /// using the specified <paramref name="url"/>.
+        /// </summary>
+        /// <param name="url">The URL to redirect to.</param>
+        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        public static IResult RedirectPermanentPreserveMethod(string url)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(url));
+            }
+
+            return new RedirectResult(url: url, permanent: true, preserveMethod: true);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="LocalRedirectResult"/> object that redirects 
+        /// (<see cref="StatusCodes.Status302Found"/>) to the specified local <paramref name="localUrl"/>.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        public static IResult LocalRedirect(string localUrl)
+        {
+            if (string.IsNullOrEmpty(localUrl))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+            }
+
+            return new LocalRedirectResult(localUrl);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
+        /// true (<see cref="StatusCodes.Status301MovedPermanently"/>) using the specified <paramref name="localUrl"/>.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        public static IResult LocalRedirectPermanent(string localUrl)
+        {
+            if (string.IsNullOrEmpty(localUrl))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+            }
+
+            return new LocalRedirectResult(localUrl, permanent: true);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
+        /// false and <see cref="LocalRedirectResult.PreserveMethod"/> set to true 
+        /// (<see cref="StatusCodes.Status307TemporaryRedirect"/>) using the specified <paramref name="localUrl"/>.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        public static IResult LocalRedirectPreserveMethod(string localUrl)
+        {
+            if (string.IsNullOrEmpty(localUrl))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+            }
+
+            return new LocalRedirectResult(localUrl: localUrl, permanent: false, preserveMethod: true);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
+        /// true and <see cref="LocalRedirectResult.PreserveMethod"/> set to true 
+        /// (<see cref="StatusCodes.Status308PermanentRedirect"/>) using the specified <paramref name="localUrl"/>.
+        /// </summary>
+        /// <param name="localUrl">The local URL to redirect to.</param>
+        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        public static IResult LocalRedirectPermanentPreserveMethod(string localUrl)
+        {
+            if (string.IsNullOrEmpty(localUrl))
+            {
+                throw new ArgumentException("Argument cannot be null or empty.", nameof(localUrl));
+            }
+
+            return new LocalRedirectResult(localUrl: localUrl, permanent: true, preserveMethod: true);
+        }
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified <paramref name="routeName"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoute(string? routeName)
+            => RedirectToRoute(routeName, routeValues: null);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified <paramref name="routeValues"/>.
+        /// </summary>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoute(object? routeValues)
+            => RedirectToRoute(routeName: null, routeValues: routeValues);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified
+        /// <paramref name="routeName"/> and <paramref name="routeValues"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoute(string? routeName, object? routeValues)
+            => RedirectToRoute(routeName, routeValues, fragment: null);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified
+        /// <paramref name="routeName"/> and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoute(string? routeName, string? fragment)
+            => RedirectToRoute(routeName, routeValues: null, fragment: fragment);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified
+        /// <paramref name="routeName"/>, <paramref name="routeValues"/>, and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoute(
+            string? routeName,
+            object? routeValues,
+            string? fragment)
+        {
+            return new RedirectToRouteResult(routeName, routeValues, fragment);
+        }
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status307TemporaryRedirect"/>) to the specified route with 
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to false and <see cref="RedirectToRouteResult.PreserveMethod"/>
+        /// set to true, using the specified <paramref name="routeName"/>, <paramref name="routeValues"/>, and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>       
+        public static IResult RedirectToRoutePreserveMethod(
+            string? routeName = null,
+            object? routeValues = null,
+            string? fragment = null)
+        {
+            return new RedirectToRouteResult(
+                routeName: routeName,
+                routeValues: routeValues,
+                permanent: false,
+                preserveMethod: true,
+                fragment: fragment);
+        }
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified route with 
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeName"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoutePermanent(string? routeName)
+            => RedirectToRoutePermanent(routeName, routeValues: null);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified route with 
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeValues"/>.
+        /// </summary>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoutePermanent(object? routeValues)
+            => RedirectToRoutePermanent(routeName: null, routeValues: routeValues);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified route with
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeName"/>
+        /// and <paramref name="routeValues"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoutePermanent(string? routeName, object? routeValues)
+            => RedirectToRoutePermanent(routeName, routeValues, fragment: null);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified route with 
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeName"/>
+        /// and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoutePermanent(string? routeName, string? fragment)
+            => RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment);
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified route with
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeName"/>,
+        /// <paramref name="routeValues"/>, and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The parameters for a route.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        public static IResult RedirectToRoutePermanent(
+            string? routeName,
+            object? routeValues,
+            string? fragment)
+        {
+            return new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment);
+        }
+
+        /// <summary>
+        /// Redirects (<see cref="StatusCodes.Status308PermanentRedirect"/>) to the specified route with
+        /// <see cref="RedirectToRouteResult.Permanent"/> set to true and <see cref="RedirectToRouteResult.PreserveMethod"/>
+        /// set to true, using the specified <paramref name="routeName"/>, <paramref name="routeValues"/>, and <paramref name="fragment"/>.
+        /// </summary>
+        /// <param name="routeName">The name of the route.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="fragment">The fragment to add to the URL.</param>
+        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>       
+        public static IResult RedirectToRoutePermanentPreserveMethod(
+            string? routeName = null,
+            object? routeValues = null,
+            string? fragment = null)
+        {
+            return new RedirectToRouteResult(
+                routeName: routeName,
+                routeValues: routeValues,
+                permanent: true,
+                preserveMethod: true,
+                fragment: fragment);
+        }
+        #endregion
+
+        /// <summary>
+        /// Creates a <see cref="StatusCodeResult"/> object by specifying a <paramref name="statusCode"/>.
+        /// </summary>
+        /// <param name="statusCode">The status code to set on the response.</param>
+        /// <returns>The created <see cref="StatusCodeResult"/> object for the response.</returns>
+        public static IResult StatusCode(int statusCode)
+            => new StatusCodeResult(statusCode);
+
+        /// <summary>
+        /// Creates an <see cref="NotFoundResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="NotFoundResult"/> for the response.</returns>
+        public static IResult NotFound()
+            => new NotFoundResult();
+
+        /// <summary>
+        /// Creates an <see cref="NotFoundObjectResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="NotFoundObjectResult"/> for the response.</returns>
+        public static IResult NotFound(object? value)
+            => new NotFoundObjectResult(value);
+
+        /// <summary>
+        /// Creates an <see cref="UnauthorizedResult"/> that produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="UnauthorizedResult"/> for the response.</returns>
+        public static IResult Unauthorized()
+            => new UnauthorizedResult();
+
+        /// <summary>
+        /// Creates an <see cref="BadRequestResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
+        public static IResult BadRequest()
+            => new BadRequestResult();
+
+        /// <summary>
+        /// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
+        /// </summary>
+        /// <param name="error">An error object to be returned to the client.</param>
+        /// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
+        public static IResult BadRequest(object? error)
+            => new BadRequestObjectResult(error);
+
+        /// <summary>
+        /// Creates an <see cref="ConflictResult"/> that produces a <see cref="StatusCodes.Status409Conflict"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="ConflictResult"/> for the response.</returns>
+        public static IResult Conflict()
+            => new ConflictResult();
+
+        /// <summary>
+        /// Creates an <see cref="ConflictObjectResult"/> that produces a <see cref="StatusCodes.Status409Conflict"/> response.
+        /// </summary>
+        /// <param name="error">Contains errors to be returned to the client.</param>
+        /// <returns>The created <see cref="ConflictObjectResult"/> for the response.</returns>
+        public static IResult Conflict(object? error)
+            => new ConflictObjectResult(error);
+
+        /// <summary>
+        /// Creates a <see cref="NoContentResult"/> object that produces an empty
+        /// <see cref="StatusCodes.Status204NoContent"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="NoContentResult"/> object for the response.</returns>
+        public static IResult NoContent()
+            => new NoContentResult();
+
+        /// <summary>
+        /// Creates a <see cref="OkResult"/> object that produces a empty <see cref="StatusCodes.Status200OK"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="OkResult"/> for the response.</returns>
+        public static IResult Ok()
+            => new OkResult();
+
+        /// <summary>
+        /// Creates an <see cref="OkObjectResult"/> object that produces a <see cref="StatusCodes.Status200OK"/> response.
+        /// </summary>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="OkObjectResult"/> for the response.</returns>
+        public static IResult Ok(object? value)
+            => new OkObjectResult(value);
+
+        /// <summary>
+        /// Creates an <see cref="UnprocessableEntityResult"/> that produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="UnprocessableEntityResult"/> for the response.</returns>
+        public static IResult UnprocessableEntity()
+            => new UnprocessableEntityResult();
+
+        /// <summary>
+        /// Creates an <see cref="UnprocessableEntityObjectResult"/> that produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
+        /// </summary>
+        /// <param name="error">An error object to be returned to the client.</param>
+        /// <returns>The created <see cref="UnprocessableEntityObjectResult"/> for the response.</returns>
+        public static IResult UnprocessableEntity(object? error)
+            => new UnprocessableEntityObjectResult(error);
+
+        #region CreatedResult
+        /// <summary>
+        /// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// </summary>
+        /// <param name="uri">The URI at which the content has been created.</param>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
+        public static IResult Created(string uri, object? value)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            return new CreatedResult(uri, value);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// </summary>
+        /// <param name="uri">The URI at which the content has been created.</param>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
+        public static IResult Created(Uri uri, object? value)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            return new CreatedResult(uri, value);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        public static IResult CreatedAtRoute(string? routeName, object? value)
+            => CreatedAtRoute(routeName, routeValues: null, value: value);
+
+        /// <summary>
+        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// </summary>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        public static IResult CreatedAtRoute(object? routeValues, object? value)
+            => CreatedAtRoute(routeName: null, routeValues: routeValues, value: value);
+
+        /// <summary>
+        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The content value to format in the response body.</param>
+        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        public static IResult CreatedAtRoute(string? routeName, object? routeValues, object? value)
+            => new CreatedAtRouteResult(routeName, routeValues, value);
+
+        #endregion
+
+        #region AcceptedResult
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted()
+            => new AcceptedResult();
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="value">The optional content value to format in the response body.</param>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted(object? value)
+            => new AcceptedResult(location: null, value: value);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.</param>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            return new AcceptedResult(locationUri: uri, value: null);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.</param>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted(string? uri)
+            => new AcceptedResult(location: uri, value: null);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
+        /// <param name="value">The optional content value to format in the response body.</param>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted(string? uri, object? value)
+            => new AcceptedResult(uri, value);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
+        /// <param name="value">The optional content value to format in the response body.</param>
+        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        public static IResult Accepted(Uri uri, object? value)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            return new AcceptedResult(locationUri: uri, value: value);
+        }
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        public static IResult AcceptedAtRoute(object? routeValues)
+            => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        public static IResult AcceptedAtRoute(string? routeName)
+            => AcceptedAtRoute(routeName, routeValues: null, value: null);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        ///<param name="routeValues">The route data to use for generating the URL.</param>
+        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        public static IResult AcceptedAtRoute(string? routeName, object? routeValues)
+            => AcceptedAtRoute(routeName, routeValues, value: null);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The optional content value to format in the response body.</param>
+        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        public static IResult AcceptedAtRoute(object? routeValues, object? value)
+            => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value);
+
+        /// <summary>
+        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// </summary>
+        /// <param name="routeName">The name of the route to use for generating the URL.</param>
+        /// <param name="routeValues">The route data to use for generating the URL.</param>
+        /// <param name="value">The optional content value to format in the response body.</param>
+        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        public static IResult AcceptedAtRoute(string? routeName, object? routeValues, object? value)
+            => new AcceptedAtRouteResult(routeName, routeValues, value);
+        #endregion
+    }
+}
diff --git a/src/Http/Http.Results/src/SignInResult.cs b/src/Http/Http.Results/src/SignInResult.cs
new file mode 100644
index 000000000000..3e42c7d35137
--- /dev/null
+++ b/src/Http/Http.Results/src/SignInResult.cs
@@ -0,0 +1,97 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignInAsync"/>.
+    /// </summary>
+    internal sealed partial class SignInResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignInResult"/> with the
+        /// default authentication scheme.
+        /// </summary>
+        /// <param name="principal">The claims principal containing the user claims.</param>
+        public SignInResult(ClaimsPrincipal principal)
+            : this(authenticationScheme: null, principal, properties: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignInResult"/> with the
+        /// specified authentication scheme.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication scheme to use when signing in the user.</param>
+        /// <param name="principal">The claims principal containing the user claims.</param>
+        public SignInResult(string? authenticationScheme, ClaimsPrincipal principal)
+            : this(authenticationScheme, principal, properties: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignInResult"/> with the
+        /// default authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="principal">The claims principal containing the user claims.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+        public SignInResult(ClaimsPrincipal principal, AuthenticationProperties? properties)
+            : this(authenticationScheme: null, principal, properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignInResult"/> with the
+        /// specified authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication schemes to use when signing in the user.</param>
+        /// <param name="principal">The claims principal containing the user claims.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+        public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties? properties)
+        {
+            Principal = principal ?? throw new ArgumentNullException(nameof(principal));
+            AuthenticationScheme = authenticationScheme;
+            Properties = properties;
+        }
+
+        /// <summary>
+        /// Gets or sets the authentication scheme that is used to perform the sign-in operation.
+        /// </summary>
+        public string? AuthenticationScheme { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="ClaimsPrincipal"/> containing the user claims.
+        /// </summary>
+        public ClaimsPrincipal Principal { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-in operation.
+        /// </summary>
+        public AuthenticationProperties? Properties { get; set; }
+
+        /// <inheritdoc />
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignInResult>>();
+
+            Log.SignInResultExecuting(logger, AuthenticationScheme, Principal);
+
+            return httpContext.SignInAsync(AuthenticationScheme, Principal, Properties);
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing SignInResult with authentication scheme ({Scheme}) and the following principal: {Principal}.",
+                EventName = "SignInResultExecuting")]
+            public static partial void SignInResultExecuting(ILogger logger, string? scheme, ClaimsPrincipal principal);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/SignOutResult.cs b/src/Http/Http.Results/src/SignOutResult.cs
new file mode 100644
index 000000000000..6601a9eb1ed5
--- /dev/null
+++ b/src/Http/Http.Results/src/SignOutResult.cs
@@ -0,0 +1,127 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignOutAsync"/>.
+    /// </summary>
+    internal sealed partial class SignOutResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
+        /// </summary>
+        public SignOutResult()
+            : this(Array.Empty<string>())
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
+        /// specified authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+        public SignOutResult(AuthenticationProperties properties)
+            : this(Array.Empty<string>(), properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the
+        /// specified authentication scheme.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication scheme to use when signing out the user.</param>
+        public SignOutResult(string authenticationScheme)
+            : this(new[] { authenticationScheme })
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the
+        /// specified authentication schemes.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to use when signing out the user.</param>
+        public SignOutResult(IList<string> authenticationSchemes)
+            : this(authenticationSchemes, properties: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the
+        /// specified authentication scheme and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationScheme">The authentication schemes to use when signing out the user.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+        public SignOutResult(string authenticationScheme, AuthenticationProperties? properties)
+            : this(new[] { authenticationScheme }, properties)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SignOutResult"/> with the
+        /// specified authentication schemes and <paramref name="properties"/>.
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication scheme to use when signing out the user.</param>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+        public SignOutResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+        {
+            AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
+            Properties = properties;
+        }
+
+        /// <summary>
+        /// Gets or sets the authentication schemes that are challenged.
+        /// </summary>
+        public IList<string> AuthenticationSchemes { get; init; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-out operation.
+        /// </summary>
+        public AuthenticationProperties? Properties { get; init; }
+
+        /// <inheritdoc />
+        public async Task ExecuteAsync(HttpContext httpContext)
+        {
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignOutResult>>();
+
+            Log.SignOutResultExecuting(logger, AuthenticationSchemes);
+
+            if (AuthenticationSchemes.Count == 0)
+            {
+                await httpContext.SignOutAsync(Properties);
+            }
+            else
+            {
+                for (var i = 0; i < AuthenticationSchemes.Count; i++)
+                {
+                    await httpContext.SignOutAsync(AuthenticationSchemes[i], Properties);
+                }
+            }
+        }
+
+        private static partial class Log
+        {
+            public static void SignOutResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+            {
+                if (logger.IsEnabled(LogLevel.Information))
+                {
+                    SignOutResultExecuting(logger, authenticationSchemes.ToArray());
+                }
+            }
+
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing SignOutResult with authentication schemes ({Schemes}).",
+                EventName = "SignOutResultExecuting",
+                SkipEnabledCheck = true)]
+            private static partial void SignOutResultExecuting(ILogger logger, string[] schemes);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/StatusCodeResult.cs b/src/Http/Http.Results/src/StatusCodeResult.cs
new file mode 100644
index 000000000000..0c8684928446
--- /dev/null
+++ b/src/Http/Http.Results/src/StatusCodeResult.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal partial class StatusCodeResult : IResult
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StatusCodeResult"/> class
+        /// with the given <paramref name="statusCode"/>.
+        /// </summary>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        public StatusCodeResult(int statusCode)
+        {
+            StatusCode = statusCode;
+        }
+
+        /// <summary>
+        /// Gets the HTTP status code.
+        /// </summary>
+        public int StatusCode { get; }
+
+        /// <summary>
+        /// Sets the status code on the HTTP response.
+        /// </summary>
+        /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the asynchronous execute operation.</returns>
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var factory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
+            var logger = factory.CreateLogger(GetType());
+
+            Log.StatusCodeResultExecuting(logger, StatusCode);
+
+            httpContext.Response.StatusCode = StatusCode;
+            return Task.CompletedTask;
+        }
+
+        private static partial class Log
+        {
+            [LoggerMessage(1, LogLevel.Information,
+                "Executing StatusCodeResult, setting HTTP status code {StatusCode}.",
+                EventName = "StatusCodeResultExecuting")]
+            public static partial void StatusCodeResultExecuting(ILogger logger, int statusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/UnauthorizedResult.cs b/src/Http/Http.Results/src/UnauthorizedResult.cs
new file mode 100644
index 000000000000..ddf3b032e00e
--- /dev/null
+++ b/src/Http/Http.Results/src/UnauthorizedResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class UnauthorizedResult : StatusCodeResult
+    {
+        public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
new file mode 100644
index 000000000000..d188da672a78
--- /dev/null
+++ b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class UnprocessableEntityObjectResult : ObjectResult
+    {
+        public UnprocessableEntityObjectResult(object? error)
+            : base(error, StatusCodes.Status422UnprocessableEntity)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityResult.cs b/src/Http/Http.Results/src/UnprocessableEntityResult.cs
new file mode 100644
index 000000000000..f58c43059e94
--- /dev/null
+++ b/src/Http/Http.Results/src/UnprocessableEntityResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class UnprocessableEntityResult : StatusCodeResult
+    {
+        public UnprocessableEntityResult() : base(StatusCodes.Status422UnprocessableEntity)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http.Results/src/VirtualFileResult.cs b/src/Http/Http.Results/src/VirtualFileResult.cs
new file mode 100644
index 000000000000..975026f35298
--- /dev/null
+++ b/src/Http/Http.Results/src/VirtualFileResult.cs
@@ -0,0 +1,129 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    /// <summary>
+    /// A <see cref="FileResult" /> that on execution writes the file specified using a virtual path to the response
+    /// using mechanisms provided by the host.
+    /// </summary>
+    internal sealed class VirtualFileResult : FileResult, IResult
+    {
+        private string _fileName;
+
+        /// <summary>
+        /// Creates a new <see cref="VirtualFileResult"/> instance with the provided <paramref name="fileName"/>
+        /// and the provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public VirtualFileResult(string fileName, string contentType)
+            : this(fileName, MediaTypeHeaderValue.Parse(contentType))
+        {
+        }
+
+        /// <summary>
+        /// Creates a new <see cref="VirtualFileResult"/> instance with
+        /// the provided <paramref name="fileName"/> and the
+        /// provided <paramref name="contentType"/>.
+        /// </summary>
+        /// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
+        /// <param name="contentType">The Content-Type header of the response.</param>
+        public VirtualFileResult(string fileName, MediaTypeHeaderValue contentType)
+            : base(contentType.ToString())
+        {
+            FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
+        }
+
+        /// <summary>
+        /// Gets or sets the path to the file that will be sent back as the response.
+        /// </summary>
+        public string FileName
+        {
+            get => _fileName;
+            [MemberNotNull(nameof(_fileName))]
+            set => _fileName = value ?? throw new ArgumentNullException(nameof(value));
+        }
+
+        /// <inheritdoc/>
+        public Task ExecuteAsync(HttpContext httpContext)
+        {
+            var hostingEnvironment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
+            var logger = httpContext.RequestServices.GetRequiredService<ILogger<VirtualFileResult>>();
+
+            var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
+            if (!fileInfo.Exists)
+            {
+                throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
+            }
+
+            Log.ExecutingFileResult(logger, this);
+
+            var lastModified = LastModified ?? fileInfo.LastModified;
+            var fileResultInfo = new FileResultInfo
+            {
+                ContentType = ContentType,
+                FileDownloadName = FileDownloadName,
+                EnableRangeProcessing = EnableRangeProcessing,
+                EntityTag = EntityTag,
+                LastModified = lastModified,
+            };
+
+            var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+                httpContext,
+                fileResultInfo,
+                fileInfo.Length,
+                EnableRangeProcessing,
+                lastModified,
+                EntityTag,
+                logger);
+
+            if (!serveBody)
+            {
+                return Task.CompletedTask;
+            }
+
+            if (range != null)
+            {
+                FileResultHelper.Log.WritingRangeToBody(logger);
+            }
+
+            var response = httpContext.Response;
+            var offset = 0L;
+            var count = (long?)null;
+            if (range != null)
+            {
+                offset = range.From ?? 0L;
+                count = rangeLength;
+            }
+
+            return response.SendFileAsync(
+                fileInfo,
+                offset,
+                count);
+        }
+
+        internal IFileInfo GetFileInformation(IFileProvider fileProvider)
+        {
+            var normalizedPath = FileName;
+            if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
+            {
+                normalizedPath = normalizedPath.Substring(1);
+            }
+
+            var fileInfo = fileProvider.GetFileInfo(normalizedPath);
+            return fileInfo;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
new file mode 100644
index 000000000000..cca95a941621
--- /dev/null
+++ b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
@@ -0,0 +1,120 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class AcceptedAtRouteResultTests
+    {
+        [Fact]
+        public async Task ExecuteResultAsync_FormatsData()
+        {
+            // Arrange
+            var url = "testAction";
+            var linkGenerator = new TestLinkGenerator { Url = url };
+            var httpContext = GetHttpContext(linkGenerator);
+            var stream = new MemoryStream();
+            httpContext.Response.Body = stream;
+
+            var routeValues = new RouteValueDictionary(new Dictionary<string, string>()
+            {
+                { "test", "case" },
+                { "sample", "route" }
+            });
+
+            // Act
+            var result = new AcceptedAtRouteResult(
+                routeName: "sample",
+                routeValues: routeValues,
+                value: "Hello world");
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            var response = Encoding.UTF8.GetString(stream.ToArray());
+            Assert.Equal("\"Hello world\"", response);
+        }
+
+        public static TheoryData<object> AcceptedAtRouteData
+        {
+            get
+            {
+                return new TheoryData<object>
+                {
+                    null,
+                    new Dictionary<string, string>()
+                    {
+                        { "hello", "world" }
+                    },
+                    new RouteValueDictionary(
+                        new Dictionary<string, string>()
+                        {
+                            { "test", "case" },
+                            { "sample", "route" }
+                        }),
+                    };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(AcceptedAtRouteData))]
+        public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader(object values)
+        {
+            // Arrange
+            var expectedUrl = "testAction";
+            var linkGenerator = new TestLinkGenerator { Url = expectedUrl };
+            var httpContext = GetHttpContext(linkGenerator);
+
+            // Act
+            var result = new AcceptedAtRouteResult(routeValues: values, value: null);
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
+            Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_ThrowsIfRouteUrlIsNull()
+        {
+            // Arrange
+            var linkGenerator = new TestLinkGenerator();
+            var httpContext = GetHttpContext(linkGenerator);
+
+            // Act
+            var result = new AcceptedAtRouteResult(
+                routeName: null,
+                routeValues: new Dictionary<string, object>(),
+                value: null);
+
+            // Assert
+            await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() =>
+                result.ExecuteAsync(httpContext),
+                "No route matches the supplied values.");
+        }
+
+        private static HttpContext GetHttpContext(LinkGenerator linkGenerator)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices(linkGenerator);
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices(LinkGenerator linkGenerator)
+        {
+            var services = new ServiceCollection();
+            services.AddLogging();
+            services.AddSingleton(linkGenerator);
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/AcceptedResultTests.cs b/src/Http/Http.Results/test/AcceptedResultTests.cs
new file mode 100644
index 000000000000..7ba7d9c8bc0c
--- /dev/null
+++ b/src/Http/Http.Results/test/AcceptedResultTests.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class AcceptedResultTests
+    {
+        [Fact]
+        public async Task ExecuteResultAsync_FormatsData()
+        {
+            // Arrange
+            var httpContext = GetHttpContext();
+            var stream = new MemoryStream();
+            httpContext.Response.Body = stream;
+            // Act
+            var result = new AcceptedResult("my-location", value: "Hello world");
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            var response = Encoding.UTF8.GetString(stream.ToArray());
+            Assert.Equal("\"Hello world\"", response);
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
+        {
+            // Arrange
+            var expectedUrl = "testAction";
+            var httpContext = GetHttpContext();
+
+            // Act
+            var result = new AcceptedResult(expectedUrl, value: "some-value");
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
+            Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices();
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddLogging();
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/BadRequestObjectResultTests.cs b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
new file mode 100644
index 000000000000..6fb262fa1847
--- /dev/null
+++ b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class BadRequestObjectResultTests
+    {
+        [Fact]
+        public void BadRequestObjectResult_SetsStatusCodeAndValue()
+        {
+            // Arrange & Act
+            var obj = new object();
+            var badRequestObjectResult = new BadRequestObjectResult(obj);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status400BadRequest, badRequestObjectResult.StatusCode);
+            Assert.Equal(obj, badRequestObjectResult.Value);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/BadRequestResultTests.cs b/src/Http/Http.Results/test/BadRequestResultTests.cs
new file mode 100644
index 000000000000..9e5dd8f6aead
--- /dev/null
+++ b/src/Http/Http.Results/test/BadRequestResultTests.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class BadRequestResultTests
+    {
+        [Fact]
+        public void BadRequestResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var badRequest = new BadRequestResult();
+
+            // Assert
+            Assert.Equal(StatusCodes.Status400BadRequest, badRequest.StatusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/ChallengeResultTest.cs b/src/Http/Http.Results/test/ChallengeResultTest.cs
new file mode 100644
index 000000000000..caecfc3d548c
--- /dev/null
+++ b/src/Http/Http.Results/test/ChallengeResultTest.cs
@@ -0,0 +1,62 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ChallengeResultTest
+    {
+        [Fact]
+        public async Task ChallengeResult_ExecuteAsync()
+        {
+            // Arrange
+            var result = new ChallengeResult("", null);
+            var auth = new Mock<IAuthenticationService>();
+            var httpContext = GetHttpContext(auth);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify(c => c.ChallengeAsync(httpContext, "", null), Times.Exactly(1));
+        }
+
+        [Fact]
+        public async Task ChallengeResult_ExecuteAsync_NoSchemes()
+        {
+            // Arrange
+            var result = new ChallengeResult(new string[] { }, null);
+            var auth = new Mock<IAuthenticationService>();
+            var httpContext = GetHttpContext(auth);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify(c => c.ChallengeAsync(httpContext, null, null), Times.Exactly(1));
+        }
+        
+        private static DefaultHttpContext GetHttpContext(Mock<IAuthenticationService> auth)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices()
+                .AddSingleton(auth.Object)
+                .BuildServiceProvider();
+            return httpContext;
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/ConflictObjectResultTest.cs b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
new file mode 100644
index 000000000000..9be97df49de4
--- /dev/null
+++ b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ConflictObjectResultTest
+    {
+        [Fact]
+        public void ConflictObjectResult_SetsStatusCodeAndValue()
+        {
+            // Arrange & Act
+            var obj = new object();
+            var conflictObjectResult = new ConflictObjectResult(obj);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status409Conflict, conflictObjectResult.StatusCode);
+            Assert.Equal(obj, conflictObjectResult.Value);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/ConflictResultTest.cs b/src/Http/Http.Results/test/ConflictResultTest.cs
new file mode 100644
index 000000000000..d21eb6fc2dc8
--- /dev/null
+++ b/src/Http/Http.Results/test/ConflictResultTest.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ConflictResultTest
+    {
+        [Fact]
+        public void ConflictResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var conflictResult = new ConflictResult();
+
+            // Assert
+            Assert.Equal(StatusCodes.Status409Conflict, conflictResult.StatusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/ContentResultTest.cs b/src/Http/Http.Results/test/ContentResultTest.cs
new file mode 100644
index 000000000000..d67688276342
--- /dev/null
+++ b/src/Http/Http.Results/test/ContentResultTest.cs
@@ -0,0 +1,152 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ContentResultTest
+    {
+        [Fact]
+        public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
+        {
+            // Arrange
+            var contentResult = new ContentResult
+            {
+                Content = null,
+                ContentType = new MediaTypeHeaderValue("text/plain")
+                {
+                    Encoding = Encoding.Unicode
+                }.ToString()
+            };
+            var httpContext = GetHttpContext();
+
+            // Act
+            await contentResult.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
+        }
+
+        public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
+        {
+            get
+            {
+                // contentType, content, responseContentType, expectedContentType, expectedData
+                return new TheoryData<MediaTypeHeaderValue, string, string, string, byte[]>
+                {
+                    {
+                        null,
+                        "κόσμε",
+                        null,
+                        "text/plain; charset=utf-8",
+                        new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
+                    },
+                    {
+                        new MediaTypeHeaderValue("text/foo"),
+                        "κόσμε",
+                        null,
+                        "text/foo",
+                        new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
+                    },
+                    {
+                        MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
+                        "κόσμε",
+                        null,
+                        "text/foo; p1=p1-value",
+                        new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
+                    },
+                    {
+                        new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
+                        "abcd",
+                        null,
+                        "text/foo; charset=us-ascii",
+                        new byte[] { 97, 98, 99, 100 }
+                    },
+                    {
+                        null,
+                        "abcd",
+                        "text/bar",
+                        "text/bar",
+                        new byte[] { 97, 98, 99, 100 }
+                    },
+                    {
+                        null,
+                        "abcd",
+                        "application/xml; charset=us-ascii",
+                        "application/xml; charset=us-ascii",
+                        new byte[] { 97, 98, 99, 100 }
+                    },
+                    {
+                        null,
+                        "abcd",
+                        "Invalid content type",
+                        "Invalid content type",
+                        new byte[] { 97, 98, 99, 100 }
+                    },
+                    {
+                        new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
+                        "abcd",
+                        "text/bar",
+                        "text/foo; charset=us-ascii",
+                        new byte[] { 97, 98, 99, 100 }
+                    },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(ContentResultContentTypeData))]
+        public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnResponse(
+            MediaTypeHeaderValue contentType,
+            string content,
+            string responseContentType,
+            string expectedContentType,
+            byte[] expectedContentData)
+        {
+            // Arrange
+            var contentResult = new ContentResult
+            {
+                Content = content,
+                ContentType = contentType?.ToString()
+            };
+            var httpContext = GetHttpContext();
+            var memoryStream = new MemoryStream();
+            httpContext.Response.Body = memoryStream;
+            httpContext.Response.ContentType = responseContentType;
+
+            // Act
+            await contentResult.ExecuteAsync(httpContext);
+
+            // Assert
+            var finalResponseContentType = httpContext.Response.ContentType;
+            Assert.Equal(expectedContentType, finalResponseContentType);
+            Assert.Equal(expectedContentData, memoryStream.ToArray());
+            Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var services = CreateServices();
+
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = services.BuildServiceProvider();
+
+            return httpContext;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
new file mode 100644
index 000000000000..ad6d0e7430c4
--- /dev/null
+++ b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
@@ -0,0 +1,93 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public partial class CreatedAtRouteResultTests
+    {
+        public static IEnumerable<object[]> CreatedAtRouteData
+        {
+            get
+            {
+                yield return new object[] { null };
+                yield return
+                    new object[] {
+                        new Dictionary<string, string>() { { "hello", "world" } }
+                    };
+                yield return
+                    new object[] {
+                        new RouteValueDictionary(new Dictionary<string, string>() {
+                            { "test", "case" },
+                            { "sample", "route" }
+                        })
+                    };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(CreatedAtRouteData))]
+        public async Task CreatedAtRouteResult_ReturnsStatusCode_SetsLocationHeader(object values)
+        {
+            // Arrange
+            var expectedUrl = "testAction";
+            var httpContext = GetHttpContext(expectedUrl);
+
+            // Act
+            var result = new CreatedAtRouteResult(routeName: null, routeValues: values, value: null);
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+            Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        [Fact]
+        public async Task CreatedAtRouteResult_ThrowsOnNullUrl()
+        {
+            // Arrange
+            var httpContext = GetHttpContext(expectedUrl: null);
+
+            var result = new CreatedAtRouteResult(
+                routeName: null,
+                routeValues: new Dictionary<string, object>(),
+                value: null);
+
+            // Act & Assert
+            await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
+                async () => await result.ExecuteAsync(httpContext),
+            "No route matches the supplied values.");
+        }
+
+        private static HttpContext GetHttpContext(string expectedUrl)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Request.PathBase = new PathString("");
+            httpContext.Response.Body = new MemoryStream();
+            httpContext.RequestServices = CreateServices(expectedUrl);
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices(string expectedUrl)
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+            services.AddSingleton<LinkGenerator>(new TestLinkGenerator
+            {
+                Url = expectedUrl
+            });
+
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/CreatedResultTest.cs b/src/Http/Http.Results/test/CreatedResultTest.cs
new file mode 100644
index 000000000000..efe27830b3a4
--- /dev/null
+++ b/src/Http/Http.Results/test/CreatedResultTest.cs
@@ -0,0 +1,79 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class CreatedResultTests
+    {
+        [Fact]
+        public void CreatedResult_SetsLocation()
+        {
+            // Arrange
+            var location = "http://test/location";
+
+            // Act
+            var result = new CreatedResult(location, "testInput");
+
+            // Assert
+            Assert.Same(location, result.Location);
+        }
+
+        [Fact]
+        public async Task CreatedResult_ReturnsStatusCode_SetsLocationHeader()
+        {
+            // Arrange
+            var location = "/test/";
+            var httpContext = GetHttpContext();
+            var result = new CreatedResult(location, "testInput");
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+            Assert.Equal(location, httpContext.Response.Headers["Location"]);
+        }
+
+        [Fact]
+        public async Task CreatedResult_OverwritesLocationHeader()
+        {
+            // Arrange
+            var location = "/test/";
+            var httpContext = GetHttpContext();
+            httpContext.Response.Headers["Location"] = "/different/location/";
+            var result = new CreatedResult(location, "testInput");
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+            Assert.Equal(location, httpContext.Response.Headers["Location"]);
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Request.PathBase = new PathString("");
+            httpContext.Response.Body = new MemoryStream();
+            httpContext.RequestServices = CreateServices();
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/FileContentResultTest.cs b/src/Http/Http.Results/test/FileContentResultTest.cs
new file mode 100644
index 000000000000..1822cb08bc29
--- /dev/null
+++ b/src/Http/Http.Results/test/FileContentResultTest.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class FileContentResultTest : FileContentResultTestBase
+    {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            byte[] buffer,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            var result = new FileContentResult(buffer, contentType)
+            {
+                EntityTag = entityTag,
+                LastModified = lastModified,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+            httpContext.RequestServices = new ServiceCollection()
+                .AddSingleton(typeof(ILogger<>), typeof(NullLogger<>))
+                .BuildServiceProvider();
+
+            return result.ExecuteAsync(httpContext);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/FileStreamResultTest.cs b/src/Http/Http.Results/test/FileStreamResultTest.cs
new file mode 100644
index 000000000000..d7e712d7a53e
--- /dev/null
+++ b/src/Http/Http.Results/test/FileStreamResultTest.cs
@@ -0,0 +1,86 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class FileStreamResultTest : FileStreamResultTestBase
+    {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            Stream stream,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            var fileStreamResult = new FileStreamResult(stream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing
+            };
+
+            return fileStreamResult.ExecuteAsync(httpContext);
+        }
+
+        [Fact]
+        public void Constructor_SetsFileName()
+        {
+            // Arrange
+            var stream = Stream.Null;
+
+            // Act
+            var result = new FileStreamResult(stream, "text/plain");
+
+            // Assert
+            Assert.Equal(stream, result.FileStream);
+        }
+
+        [Fact]
+        public void Constructor_SetsContentTypeAndParameters()
+        {
+            // Arrange
+            var stream = Stream.Null;
+            var contentType = "text/plain; charset=us-ascii; p1=p1-value";
+            var expectedMediaType = contentType;
+
+            // Act
+            var result = new FileStreamResult(stream, contentType);
+
+            // Assert
+            Assert.Equal(stream, result.FileStream);
+            Assert.Equal(expectedMediaType, result.ContentType);
+        }
+
+        [Fact]
+        public void Constructor_SetsLastModifiedAndEtag()
+        {
+            // Arrange
+            var stream = Stream.Null;
+            var contentType = "text/plain";
+            var expectedMediaType = contentType;
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+
+            // Act
+            var result = new FileStreamResult(stream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+            };
+
+            // Assert
+            Assert.Equal(lastModified, result.LastModified);
+            Assert.Equal(entityTag, result.EntityTag);
+            Assert.Equal(expectedMediaType, result.ContentType);
+        }
+
+    }
+}
diff --git a/src/Http/Http.Results/test/ForbidResultTest.cs b/src/Http/Http.Results/test/ForbidResultTest.cs
new file mode 100644
index 000000000000..4ce720564892
--- /dev/null
+++ b/src/Http/Http.Results/test/ForbidResultTest.cs
@@ -0,0 +1,129 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ForbidResultTest
+    {
+        [Fact]
+        public async Task ExecuteResultAsync_InvokesForbidAsyncOnAuthenticationService()
+        {
+            // Arrange
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "", null))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new ForbidResult("", null);
+            
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_InvokesForbidAsyncOnAllConfiguredSchemes()
+        {
+            // Arrange
+            var authProperties = new AuthenticationProperties();
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            auth
+                .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new ForbidResult(new[] { "Scheme1", "Scheme2" }, authProperties);
+            var routeData = new RouteData();
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        public static TheoryData ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData =>
+            new TheoryData<AuthenticationProperties>
+            {
+                null,
+                new AuthenticationProperties()
+            };
+
+        [Theory]
+        [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
+        public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties(AuthenticationProperties expected)
+        {
+            // Arrange
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var result = new ForbidResult(expected);
+            var httpContext = GetHttpContext(auth.Object);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Theory]
+        [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
+        public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties_WhenAuthenticationSchemesIsEmpty(
+            AuthenticationProperties expected)
+        {
+            // Arrange
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new ForbidResult(expected)
+            {
+                AuthenticationSchemes = new string[0]
+            };
+            var routeData = new RouteData();
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices()
+                .AddSingleton(auth)
+                .BuildServiceProvider();
+            return httpContext;
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/LocalRedirectResultTest.cs b/src/Http/Http.Results/test/LocalRedirectResultTest.cs
new file mode 100644
index 000000000000..283148da985c
--- /dev/null
+++ b/src/Http/Http.Results/test/LocalRedirectResultTest.cs
@@ -0,0 +1,138 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class LocalRedirectResultTest
+    {
+        [Fact]
+        public void Constructor_WithParameterUrl_SetsResultUrlAndNotPermanentOrPreserveMethod()
+        {
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new LocalRedirectResult(url);
+
+            // Assert
+            Assert.False(result.PreserveMethod);
+            Assert.False(result.Permanent);
+            Assert.Same(url, result.Url);
+        }
+
+        [Fact]
+        public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlAndPermanentNotPreserveMethod()
+        {
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new LocalRedirectResult(url, permanent: true);
+
+            // Assert
+            Assert.False(result.PreserveMethod);
+            Assert.True(result.Permanent);
+            Assert.Same(url, result.Url);
+        }
+
+        [Fact]
+        public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlPermanentAndPreserveMethod()
+        {
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new LocalRedirectResult(url, permanent: true, preserveMethod: true);
+
+            // Assert
+            Assert.True(result.PreserveMethod);
+            Assert.True(result.Permanent);
+            Assert.Same(url, result.Url);
+        }
+
+        [Fact]
+        public async Task Execute_ReturnsExpectedValues()
+        {
+            // Arrange
+            var appRoot = "/";
+            var contentPath = "~/Home/About";
+            var expectedPath = "/Home/About";
+
+            var httpContext = GetHttpContext(appRoot);
+            var result = new LocalRedirectResult(contentPath);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(expectedPath, httpContext.Response.Headers.Location.ToString());
+            Assert.Equal(StatusCodes.Status302Found, httpContext.Response.StatusCode);
+        }
+
+        [Theory]
+        [InlineData("", "//")]
+        [InlineData("", "/\\")]
+        [InlineData("", "//foo")]
+        [InlineData("", "/\\foo")]
+        [InlineData("", "Home/About")]
+        [InlineData("/myapproot", "http://www.example.com")]
+        public async Task Execute_Throws_ForNonLocalUrl(
+            string appRoot,
+            string contentPath)
+        {
+            // Arrange
+            var httpContext = GetHttpContext(appRoot);
+            var result = new LocalRedirectResult(contentPath);
+
+            // Act & Assert
+            var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
+            Assert.Equal(
+                "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
+                "have a host/authority part. URLs using virtual paths ('~/') are also local.",
+                exception.Message);
+        }
+
+        [Theory]
+        [InlineData("", "~//")]
+        [InlineData("", "~/\\")]
+        [InlineData("", "~//foo")]
+        [InlineData("", "~/\\foo")]
+        public async Task Execute_Throws_ForNonLocalUrlTilde(
+            string appRoot,
+            string contentPath)
+        {
+            // Arrange
+            var httpContext = GetHttpContext(appRoot);
+            var result = new LocalRedirectResult(contentPath);
+
+            // Act & Assert
+            var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
+            Assert.Equal(
+                "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
+                "have a host/authority part. URLs using virtual paths ('~/') are also local.",
+                exception.Message);
+        }
+
+        private static IServiceProvider GetServiceProvider()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddTransient(typeof(ILogger<>), typeof(NullLogger<>));
+            return serviceCollection.BuildServiceProvider();
+        }
+
+        private static HttpContext GetHttpContext(string appRoot)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = GetServiceProvider();
+            httpContext.Request.PathBase = new PathString(appRoot);
+            return httpContext;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/Microsoft.AspNetCore.Http.Results.Tests.csproj b/src/Http/Http.Results/test/Microsoft.AspNetCore.Http.Results.Tests.csproj
new file mode 100644
index 000000000000..270acfe04ebb
--- /dev/null
+++ b/src/Http/Http.Results/test/Microsoft.AspNetCore.Http.Results.Tests.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.AspNetCore.Http.Results" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+
+    <Compile Include="$(SharedSourceRoot)ResultsTests\*.cs" LinkBase="Shared" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Results/test/NotFoundObjectResultTest.cs b/src/Http/Http.Results/test/NotFoundObjectResultTest.cs
new file mode 100644
index 000000000000..be09b3660270
--- /dev/null
+++ b/src/Http/Http.Results/test/NotFoundObjectResultTest.cs
@@ -0,0 +1,67 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class NotFoundObjectResultTest
+    {
+        [Fact]
+        public void NotFoundObjectResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var notFound = new NotFoundObjectResult(null);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+        }
+
+        [Fact]
+        public void NotFoundObjectResult_InitializesStatusCodeAndResponseContent()
+        {
+            // Arrange & act
+            var notFound = new NotFoundObjectResult("Test Content");
+
+            // Assert
+            Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+            Assert.Equal("Test Content", notFound.Value);
+        }
+
+        [Fact]
+        public async Task NotFoundObjectResult_ExecuteSuccessful()
+        {
+            // Arrange
+            var httpContext = GetHttpContext();
+            var result = new NotFoundObjectResult("Test Content");
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Request.PathBase = new PathString("");
+            httpContext.Response.Body = new MemoryStream();
+            httpContext.RequestServices = CreateServices();
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/NotFoundResultTests.cs b/src/Http/Http.Results/test/NotFoundResultTests.cs
new file mode 100644
index 000000000000..ffa21c67a33b
--- /dev/null
+++ b/src/Http/Http.Results/test/NotFoundResultTests.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class NotFoundResultTests
+    {
+        [Fact]
+        public void NotFoundResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var notFound = new NotFoundResult();
+
+            // Assert
+            Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/ObjectResultTests.cs b/src/Http/Http.Results/test/ObjectResultTests.cs
new file mode 100644
index 000000000000..3588eb28aeda
--- /dev/null
+++ b/src/Http/Http.Results/test/ObjectResultTests.cs
@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class ObjectResultTests
+    {
+        [Fact]
+        public async Task ObjectResult_ExecuteAsync_SetsStatusCode()
+        {
+            // Arrange
+            var result = new ObjectResult("Hello", 407);
+
+            var httpContext = new DefaultHttpContext()
+            {
+                RequestServices = CreateServices(),
+            };
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(407, httpContext.Response.StatusCode);
+        }
+
+        [Fact]
+        public async Task ObjectResult_ExecuteAsync_JsonSerializesBody()
+        {
+            // Arrange
+            var result = new ObjectResult("Hello", 407);
+            var stream = new MemoryStream();
+            var httpContext = new DefaultHttpContext()
+            {
+                RequestServices = CreateServices(),
+                Response =
+                {
+                    Body = stream,
+                },
+            };
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/OkObjectResultTest.cs b/src/Http/Http.Results/test/OkObjectResultTest.cs
new file mode 100644
index 000000000000..5ba062ed8dd8
--- /dev/null
+++ b/src/Http/Http.Results/test/OkObjectResultTest.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class OkObjectResultTest
+    {
+        [Fact]
+        public async Task OkObjectResult_SetsStatusCodeAndValue()
+        {
+            // Arrange
+            var result = new OkObjectResult("Hello world");
+            var httpContext = GetHttpContext();
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode);
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Request.PathBase = new PathString("");
+            httpContext.Response.Body = new MemoryStream();
+            httpContext.RequestServices = CreateServices();
+            return httpContext;
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/OkResultTest.cs b/src/Http/Http.Results/test/OkResultTest.cs
new file mode 100644
index 000000000000..a32ca95201e0
--- /dev/null
+++ b/src/Http/Http.Results/test/OkResultTest.cs
@@ -0,0 +1,37 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class OkResultTest
+    {
+        [Fact]
+        public async Task HttpOkResult_SetsStatusCode()
+        {
+            // Arrange
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices().BuildServiceProvider();
+
+            var result = new OkResult();
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode);
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/PhysicalFileResultTest.cs b/src/Http/Http.Results/test/PhysicalFileResultTest.cs
new file mode 100644
index 000000000000..bf1fdd1b4654
--- /dev/null
+++ b/src/Http/Http.Results/test/PhysicalFileResultTest.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class PhysicalFileResultTest : PhysicalFileResultTestBase
+    {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            string path,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            var fileResult = new PhysicalFileResult(path, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+                GetFileInfoWrapper = (path) =>
+                {
+                    var lastModified = DateTimeOffset.MinValue.AddDays(1);
+                    return new()
+                    {
+                        Exists = true,
+                        Length = 34,
+                        LastWriteTimeUtc = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0))
+                    };
+                }
+            };
+
+            return fileResult.ExecuteAsync(httpContext);
+        }
+    }
+}
diff --git a/src/Mvc/Mvc.Core/test/RedirectActionResultTest.cs b/src/Http/Http.Results/test/RedirectResultTest.cs
similarity index 50%
rename from src/Mvc/Mvc.Core/test/RedirectActionResultTest.cs
rename to src/Http/Http.Results/test/RedirectResultTest.cs
index 9b139431fd88..005407107c5c 100644
--- a/src/Mvc/Mvc.Core/test/RedirectActionResultTest.cs
+++ b/src/Http/Http.Results/test/RedirectResultTest.cs
@@ -1,13 +1,13 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Http.Result
 {
-    public class RedirectActionResultTest
+    public class RedirectResultTest : RedirectResultTestBase
     {
         [Fact]
         public void RedirectResult_Constructor_WithParameterUrl_SetsResultUrlAndNotPermanentOrPreserveMethod()
@@ -54,43 +54,10 @@ public void RedirectResult_Constructor_WithParameterUrlPermanentAndPreservesMeth
             Assert.Same(url, result.Url);
         }
 
-        [Theory]
-        [InlineData("", "/Home/About", "/Home/About")]
-        [InlineData("/myapproot", "/test", "/test")]
-        public async Task Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde(
-            string appRoot,
-            string contentPath,
-            string expectedPath)
+        protected override Task ExecuteAsync(HttpContext httpContext, string contentPath)
         {
-            var action
-                = new Func<RedirectResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseRedirectResultTest.Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde(
-                appRoot,
-                contentPath,
-                expectedPath,
-                action);
-        }
-
-        [Theory]
-        [InlineData(null, "~/Home/About", "/Home/About")]
-        [InlineData("/", "~/Home/About", "/Home/About")]
-        [InlineData("/", "~/", "/")]
-        [InlineData("", "~/Home/About", "/Home/About")]
-        [InlineData("/myapproot", "~/", "/myapproot/")]
-        public async Task Execute_ReturnsAppRelativePath_WhenItStartsWithTilde(
-            string appRoot,
-            string contentPath,
-            string expectedPath)
-        {
-            var action =
-                new Func<RedirectResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseRedirectResultTest.Execute_ReturnsAppRelativePath_WhenItStartsWithTilde(
-                appRoot,
-                contentPath,
-                expectedPath,
-                action);
+            var redirectResult = new RedirectResult(contentPath);
+            return redirectResult.ExecuteAsync(httpContext);
         }
     }
 }
diff --git a/src/Http/Http.Results/test/RedirectToRouteResultTest.cs b/src/Http/Http.Results/test/RedirectToRouteResultTest.cs
new file mode 100644
index 000000000000..3b43e65f301d
--- /dev/null
+++ b/src/Http/Http.Results/test/RedirectToRouteResultTest.cs
@@ -0,0 +1,111 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class RedirectToRouteResultTest
+    {
+        [Fact]
+        public async Task RedirectToRoute_Execute_ThrowsOnNullUrl()
+        {
+            // Arrange
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices(null).BuildServiceProvider();
+
+            var result = new RedirectToRouteResult(null, new Dictionary<string, object>());
+
+            // Act & Assert
+            await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
+                async () =>
+                {
+                    await result.ExecuteAsync(httpContext);
+                },
+                "No route matches the supplied values.");
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_UsesRouteName_ToGenerateLocationHeader()
+        {
+            // Arrange
+            var routeName = "orders_api";
+            var locationUrl = "/api/orders/10";
+
+            var httpContext = GetHttpContext(locationUrl);
+
+            var result = new RedirectToRouteResult(routeName, new { id = 10 });
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.True(httpContext.Response.Headers.ContainsKey("Location"), "Location header not found");
+            Assert.Equal(locationUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect()
+        {
+            // Arrange
+            var expectedUrl = "/SampleAction#test";
+            var expectedStatusCode = StatusCodes.Status301MovedPermanently;
+            var httpContext = GetHttpContext(expectedUrl);
+
+            var result = new RedirectToRouteResult("Sample", null, true, "test");
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+            Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        [Fact]
+        public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect_WithPreserveMethod()
+        {
+            // Arrange
+            var expectedUrl = "/SampleAction#test";
+            var expectedStatusCode = StatusCodes.Status308PermanentRedirect;
+
+            var httpContext = GetHttpContext(expectedUrl);
+            var result = new RedirectToRouteResult("Sample", null, true, true, "test");
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+            Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+        }
+
+        private static HttpContext GetHttpContext(string path)
+        {
+            var services = CreateServices(path);
+
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = services.BuildServiceProvider();
+
+            return httpContext;
+        }
+
+        private static IServiceCollection CreateServices(string path)
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<LinkGenerator>(new TestLinkGenerator { Url = path });
+
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/SignInResultTest.cs b/src/Http/Http.Results/test/SignInResultTest.cs
new file mode 100644
index 000000000000..0930081fc2ba
--- /dev/null
+++ b/src/Http/Http.Results/test/SignInResultTest.cs
@@ -0,0 +1,95 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class SignInResultTest
+    {
+        [Fact]
+        public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManager()
+        {
+            // Arrange
+            var principal = new ClaimsPrincipal();
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "", principal, null))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignInResult("", principal, null);
+        
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Fact]
+        public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManagerWithDefaultScheme()
+        {
+            // Arrange
+            var principal = new ClaimsPrincipal();
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), null, principal, null))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignInResult(principal);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Fact]
+        public async Task ExecuteAsync_InvokesSignInAsyncOnConfiguredScheme()
+        {
+            // Arrange
+            var principal = new ClaimsPrincipal();
+            var authProperties = new AuthenticationProperties();
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "Scheme1", principal, authProperties))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignInResult("Scheme1", principal, authProperties);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices()
+                .AddSingleton(auth)
+                .BuildServiceProvider();
+            return httpContext;
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/SignOutResultTest.cs b/src/Http/Http.Results/test/SignOutResultTest.cs
new file mode 100644
index 000000000000..47f4baaca5fc
--- /dev/null
+++ b/src/Http/Http.Results/test/SignOutResultTest.cs
@@ -0,0 +1,95 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class SignOutResultTest
+    {
+        [Fact]
+        public async Task ExecuteAsync_NoArgsInvokesDefaultSignOut()
+        {
+            // Arrange
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), null, null))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignOutResult();
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Fact]
+        public async Task ExecuteAsync_InvokesSignOutAsyncOnAuthenticationManager()
+        {
+            // Arrange
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "", null))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignOutResult("", null);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        [Fact]
+        public async Task ExecuteAsync_InvokesSignOutAsyncOnAllConfiguredSchemes()
+        {
+            // Arrange
+            var authProperties = new AuthenticationProperties();
+            var auth = new Mock<IAuthenticationService>();
+            auth
+                .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            auth
+                .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
+                .Returns(Task.CompletedTask)
+                .Verifiable();
+            var httpContext = GetHttpContext(auth.Object);
+            var result = new SignOutResult(new[] { "Scheme1", "Scheme2" }, authProperties);
+
+            // Act
+            await result.ExecuteAsync(httpContext);
+
+            // Assert
+            auth.Verify();
+        }
+
+        private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = CreateServices()
+                .AddSingleton(auth)
+                .BuildServiceProvider();
+            return httpContext;
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/StatusCodeResultTests.cs b/src/Http/Http.Results/test/StatusCodeResultTests.cs
new file mode 100644
index 000000000000..f7ae16da5e17
--- /dev/null
+++ b/src/Http/Http.Results/test/StatusCodeResultTests.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class StatusCodeResultTests
+    {
+        [Fact]
+        public void StatusCodeResult_ExecuteResultSetsResponseStatusCode()
+        {
+            // Arrange
+            var result = new StatusCodeResult(StatusCodes.Status404NotFound);
+
+            var httpContext = GetHttpContext();
+
+            // Act
+            result.ExecuteAsync(httpContext);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            return services;
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var services = CreateServices();
+
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = services.BuildServiceProvider();
+
+            return httpContext;
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/TestLinkGenerator.cs b/src/Http/Http.Results/test/TestLinkGenerator.cs
new file mode 100644
index 000000000000..fbe401fe1bc7
--- /dev/null
+++ b/src/Http/Http.Results/test/TestLinkGenerator.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Routing;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    internal sealed class TestLinkGenerator : LinkGenerator
+    {
+        public string Url { get; set; }
+
+        public override string GetPathByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override string GetPathByAddress<TAddress>(TAddress address, RouteValueDictionary values, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override string GetUriByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, string scheme = null, HostString? host = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
+            => Url;
+
+        public override string GetUriByAddress<TAddress>(TAddress address, RouteValueDictionary values, string scheme, HostString host, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
+            => Url;
+    }
+}
diff --git a/src/Http/Http.Results/test/UnauthorizedResultTests.cs b/src/Http/Http.Results/test/UnauthorizedResultTests.cs
new file mode 100644
index 000000000000..5d825bb0d7fc
--- /dev/null
+++ b/src/Http/Http.Results/test/UnauthorizedResultTests.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class UnauthorizedResultTests
+    {
+        [Fact]
+        public void UnauthorizedResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var result = new UnauthorizedResult();
+
+            // Assert
+            Assert.Equal(StatusCodes.Status401Unauthorized, result.StatusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs b/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
new file mode 100644
index 000000000000..057a8ce3b3c8
--- /dev/null
+++ b/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class UnprocessableEntityObjectResultTests
+    {
+        [Fact]
+        public void UnprocessableEntityObjectResult_SetsStatusCodeAndValue()
+        {
+            // Arrange & Act
+            var obj = new object();
+            var result = new UnprocessableEntityObjectResult(obj);
+
+            // Assert
+            Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode);
+            Assert.Equal(obj, result.Value);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs b/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs
new file mode 100644
index 000000000000..07ec867abea2
--- /dev/null
+++ b/src/Http/Http.Results/test/UnprocessableEntityResultTests.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class UnprocessableEntityResultTests
+    {
+        [Fact]
+        public void UnprocessableEntityResult_InitializesStatusCode()
+        {
+            // Arrange & act
+            var result = new UnprocessableEntityResult();
+
+            // Assert
+            Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode);
+        }
+    }
+}
diff --git a/src/Http/Http.Results/test/VirtualFileResultTest.cs b/src/Http/Http.Results/test/VirtualFileResultTest.cs
new file mode 100644
index 000000000000..51bb3c703086
--- /dev/null
+++ b/src/Http/Http.Results/test/VirtualFileResultTest.cs
@@ -0,0 +1,25 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Result
+{
+    public class VirtualFileResultTest : VirtualFileResultTestBase
+    {
+        protected override Task ExecuteAsync(HttpContext httpContext, string path, string contentType, DateTimeOffset? lastModified = null, EntityTagHeaderValue entityTag = null, bool enableRangeProcessing = false)
+        {
+            var result = new VirtualFileResult(path, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+            return result.ExecuteAsync(httpContext);
+        }
+    }
+}
diff --git a/src/Http/HttpAbstractions.slnf b/src/Http/HttpAbstractions.slnf
index 93263e734be1..ce6dca91fd62 100644
--- a/src/Http/HttpAbstractions.slnf
+++ b/src/Http/HttpAbstractions.slnf
@@ -19,6 +19,8 @@
       "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
       "src\\Http\\Http.Extensions\\test\\Microsoft.AspNetCore.Http.Extensions.Tests.csproj",
       "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
+      "src\\Http\\Http.Results\\src\\Microsoft.AspNetCore.Http.Results.csproj",
+      "src\\Http\\Http.Results\\test\\Microsoft.AspNetCore.Http.Results.Tests.csproj",
       "src\\Http\\Http\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Http.Microbenchmarks.csproj",
       "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
       "src\\Http\\Http\\test\\Microsoft.AspNetCore.Http.Tests.csproj",
diff --git a/src/Middleware/StaticFiles/src/LoggerExtensions.cs b/src/Middleware/StaticFiles/src/LoggerExtensions.cs
index b2d973bff61b..76be097d0530 100644
--- a/src/Middleware/StaticFiles/src/LoggerExtensions.cs
+++ b/src/Middleware/StaticFiles/src/LoggerExtensions.cs
@@ -12,19 +12,19 @@ namespace Microsoft.AspNetCore.StaticFiles
     /// </summary>
     internal static class LoggerExtensions
     {
-        private static Action<ILogger, string, Exception?> _methodNotSupported;
-        private static Action<ILogger, string, string, Exception?> _fileServed;
-        private static Action<ILogger, string, Exception?> _pathMismatch;
-        private static Action<ILogger, string, Exception?> _fileTypeNotSupported;
-        private static Action<ILogger, string, Exception?> _fileNotFound;
-        private static Action<ILogger, string, Exception?> _fileNotModified;
-        private static Action<ILogger, string, Exception?> _preconditionFailed;
-        private static Action<ILogger, int, string, Exception?> _handled;
-        private static Action<ILogger, string, Exception?> _rangeNotSatisfiable;
-        private static Action<ILogger, StringValues, string, Exception?> _sendingFileRange;
-        private static Action<ILogger, StringValues, string, Exception?> _copyingFileRange;
-        private static Action<ILogger, Exception?> _writeCancelled;
-        private static Action<ILogger, Exception?> _endpointMatched;
+        private static readonly Action<ILogger, string, Exception?> _methodNotSupported;
+        private static readonly Action<ILogger, string, string, Exception?> _fileServed;
+        private static readonly Action<ILogger, string, Exception?> _pathMismatch;
+        private static readonly Action<ILogger, string, Exception?> _fileTypeNotSupported;
+        private static readonly Action<ILogger, string, Exception?> _fileNotFound;
+        private static readonly Action<ILogger, string, Exception?> _fileNotModified;
+        private static readonly Action<ILogger, string, Exception?> _preconditionFailed;
+        private static readonly Action<ILogger, int, string, Exception?> _handled;
+        private static readonly Action<ILogger, string, Exception?> _rangeNotSatisfiable;
+        private static readonly Action<ILogger, StringValues, string, Exception?> _sendingFileRange;
+        private static readonly Action<ILogger, StringValues, string, Exception?> _copyingFileRange;
+        private static readonly Action<ILogger, Exception?> _writeCancelled;
+        private static readonly Action<ILogger, Exception?> _endpointMatched;
 
         static LoggerExtensions()
         {
diff --git a/src/Mvc/Mvc.Core/src/ChallengeResult.cs b/src/Mvc/Mvc.Core/src/ChallengeResult.cs
index 9add46ad3828..4b21053cac92 100644
--- a/src/Mvc/Mvc.Core/src/ChallengeResult.cs
+++ b/src/Mvc/Mvc.Core/src/ChallengeResult.cs
@@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// <summary>
     /// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
     /// </summary>
-    public class ChallengeResult : ActionResult, IResult
+    public class ChallengeResult : ActionResult
     {
         /// <summary>
         /// Initializes a new instance of <see cref="ChallengeResult"/>.
@@ -91,24 +91,14 @@ public ChallengeResult(IList<string> authenticationSchemes, AuthenticationProper
         public AuthenticationProperties? Properties { get; set; }
 
         /// <inheritdoc />
-        public override Task ExecuteResultAsync(ActionContext context)
+        public override async Task ExecuteResultAsync(ActionContext context)
         {
             if (context == null)
             {
                 throw new ArgumentNullException(nameof(context));
             }
 
-            return ExecuteAsync(context.HttpContext);
-        }
-
-        /// <inheritdoc />
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            return ExecuteAsync(httpContext);
-        }
-
-        private async Task ExecuteAsync(HttpContext httpContext)
-        {
+            var httpContext = context.HttpContext;
             var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
             var logger = loggerFactory.CreateLogger<ChallengeResult>();
 
diff --git a/src/Mvc/Mvc.Core/src/ContentResult.cs b/src/Mvc/Mvc.Core/src/ContentResult.cs
index c4d8158ac7b2..e1d86fc1f03f 100644
--- a/src/Mvc/Mvc.Core/src/ContentResult.cs
+++ b/src/Mvc/Mvc.Core/src/ContentResult.cs
@@ -3,22 +3,16 @@
 
 using System;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
 
 namespace Microsoft.AspNetCore.Mvc
 {
     /// <summary>
     /// A <see cref="ActionResult"/> that when executed will produce a response with content.
     /// </summary>
-    public class ContentResult : ActionResult, IResult, IStatusCodeActionResult
+    public class ContentResult : ActionResult, IStatusCodeActionResult
     {
-        private const string DefaultContentType = "text/plain; charset=utf-8";
-
         /// <summary>
         /// Gets or set the content representing the body of the response.
         /// </summary>
@@ -45,45 +39,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<ContentResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        /// <summary>
-        /// Writes the content to the HTTP response.
-        /// </summary>
-        /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
-        /// <returns>A task that represents the asynchronous execute operation.</returns>
-        async Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-
-            var response = httpContext.Response;
-
-            ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
-                ContentType,
-                response.ContentType,
-                DefaultContentType,
-                out var resolvedContentType,
-                out var resolvedContentTypeEncoding);
-
-            response.ContentType = resolvedContentType;
-
-            if (StatusCode != null)
-            {
-                response.StatusCode = StatusCode.Value;
-            }
-
-            var factory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
-            var logger = factory.CreateLogger<ContentResult>();
-
-            logger.ContentResultExecuting(resolvedContentType);
-
-            if (Content != null)
-            {
-                response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
-                await response.WriteAsync(Content, resolvedContentTypeEncoding);
-            }
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/FileContentResult.cs b/src/Mvc/Mvc.Core/src/FileContentResult.cs
index f9bc761110b6..7fcb8729112f 100644
--- a/src/Mvc/Mvc.Core/src/FileContentResult.cs
+++ b/src/Mvc/Mvc.Core/src/FileContentResult.cs
@@ -3,12 +3,9 @@
 
 using System;
 using System.Diagnostics.CodeAnalysis;
-using System.IO;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
 using Microsoft.Net.Http.Headers;
 
 namespace Microsoft.AspNetCore.Mvc
@@ -17,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// Represents an <see cref="ActionResult"/> that when executed will
     /// write a binary file to the response.
     /// </summary>
-    public class FileContentResult : FileResult, IResult
+    public class FileContentResult : FileResult
     {
         private byte[] _fileContents;
 
@@ -80,43 +77,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<FileContentResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-
-            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
-            var logger = loggerFactory.CreateLogger<RedirectResult>();
-
-            var (range, rangeLength, serveBody) = FileResultExecutorBase.SetHeadersAndLog(
-                httpContext,
-                this,
-                FileContents.Length,
-                EnableRangeProcessing,
-                LastModified,
-                EntityTag,
-                logger);
-
-            if (!serveBody)
-            {
-                return Task.CompletedTask;
-            }
-
-            if (range != null && rangeLength == 0)
-            {
-                return Task.CompletedTask;
-            }
-
-            if (range != null)
-            {
-                logger.WritingRangeToBody();
-            }
-
-            var fileContentStream = new MemoryStream(FileContents);
-            return FileResultExecutorBase.WriteFileAsyncInternal(httpContext, fileContentStream, range, rangeLength);
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/ForbidResult.cs b/src/Mvc/Mvc.Core/src/ForbidResult.cs
index de269350ff37..5418f7685edd 100644
--- a/src/Mvc/Mvc.Core/src/ForbidResult.cs
+++ b/src/Mvc/Mvc.Core/src/ForbidResult.cs
@@ -5,7 +5,6 @@
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 
@@ -14,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// <summary>
     /// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ForbidAsync"/>.
     /// </summary>
-    public class ForbidResult : ActionResult, IResult
+    public class ForbidResult : ActionResult
     {
         /// <summary>
         /// Initializes a new instance of <see cref="ForbidResult"/>.
@@ -91,24 +90,15 @@ public ForbidResult(IList<string> authenticationSchemes, AuthenticationPropertie
         public AuthenticationProperties? Properties { get; set; }
 
         /// <inheritdoc />
-        public override Task ExecuteResultAsync(ActionContext context)
+        public override async Task ExecuteResultAsync(ActionContext context)
         {
             if (context == null)
             {
                 throw new ArgumentNullException(nameof(context));
             }
 
-            return ExecuteAsync(context.HttpContext);
-        }
-
-        /// <inheritdoc />
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            return ExecuteAsync(httpContext);
-        }
+            var httpContext = context.HttpContext;
 
-        private async Task ExecuteAsync(HttpContext httpContext)
-        {
             var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
             var logger = loggerFactory.CreateLogger<ForbidResult>();
 
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/ContentResultExecutor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/ContentResultExecutor.cs
index 865c8b2d377a..6dd8c0b19ae6 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/ContentResultExecutor.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/ContentResultExecutor.cs
@@ -4,7 +4,9 @@
 #nullable enable
 
 using System;
+using System.Text;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Extensions.Logging;
 
@@ -48,7 +50,8 @@ public virtual async Task ExecuteAsync(ActionContext context, ContentResult resu
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 result.ContentType,
                 response.ContentType,
-                DefaultContentType,
+                (DefaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs b/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs
index 72dca6e2c2b5..3d0e63e6d573 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs
@@ -1,15 +1,10 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-#nullable enable
-
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.AspNetCore.Http.Headers;
 using Microsoft.AspNetCore.Internal;
 using Microsoft.Extensions.Logging;
 using Microsoft.Net.Http.Headers;
@@ -17,12 +12,10 @@
 namespace Microsoft.AspNetCore.Mvc.Infrastructure
 {
     /// <summary>
-    /// Base class for a file result.
+    /// Base class for executing a file result.
     /// </summary>
     public class FileResultExecutorBase
     {
-        private const string AcceptRangeHeaderValue = "bytes";
-
         /// <summary>
         /// The buffer size: 64 * 1024.
         /// </summary>
@@ -68,330 +61,16 @@ protected virtual (RangeItemHeaderValue? range, long rangeLength, bool serveBody
             DateTimeOffset? lastModified = null,
             EntityTagHeaderValue? etag = null)
         {
-            return SetHeadersAndLog(
-                context.HttpContext,
-                result,
-                fileLength,
-                enableRangeProcessing,
-                lastModified,
-                etag,
-                Logger);
-        }
-
-        internal static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetHeadersAndLog(
-            HttpContext httpContext,
-            FileResult result,
-            long? fileLength,
-            bool enableRangeProcessing,
-            DateTimeOffset? lastModified,
-            EntityTagHeaderValue? etag,
-            ILogger logger)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-            if (result == null)
-            {
-                throw new ArgumentNullException(nameof(result));
-            }
-
-            var request = httpContext.Request;
-            var httpRequestHeaders = request.GetTypedHeaders();
-
-            // Since the 'Last-Modified' and other similar http date headers are rounded down to whole seconds,
-            // round down current file's last modified to whole seconds for correct comparison.
-            if (lastModified.HasValue)
-            {
-                lastModified = RoundDownToWholeSeconds(lastModified.Value);
-            }
-
-            var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag, logger);
-
-            var response = httpContext.Response;
-            SetLastModifiedAndEtagHeaders(response, lastModified, etag);
-
-            // Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
-            if (preconditionState == PreconditionState.NotModified)
-            {
-                response.StatusCode = StatusCodes.Status304NotModified;
-                return (range: null, rangeLength: 0, serveBody: false);
-            }
-            else if (preconditionState == PreconditionState.PreconditionFailed)
-            {
-                response.StatusCode = StatusCodes.Status412PreconditionFailed;
-                return (range: null, rangeLength: 0, serveBody: false);
-            }
-
-            SetContentType(httpContext, result);
-            SetContentDispositionHeader(httpContext, result);
-
-            if (fileLength.HasValue)
-            {
-                // Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to 
-                // the length of the entire file. 
-                // If the request is a valid range request, this header is overwritten with the length of the range as part of the 
-                // range processing (see method SetContentLength).
-
-                response.ContentLength = fileLength.Value;
-
-                // Handle range request
-                if (enableRangeProcessing)
-                {
-                    SetAcceptRangeHeader(response);
-
-                    // If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid,
-                    // range should be processed and Range headers should be set
-                    if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
-                        && (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess)
-                        && (IfRangeValid(httpRequestHeaders, lastModified, etag, logger)))
-                    {
-                        return SetRangeHeaders(httpContext, httpRequestHeaders, fileLength.Value, logger);
-                    }
-                }
-                else
-                {
-                    logger.NotEnabledForRangeProcessing();
-                }
-            }
-
-            return (range: null, rangeLength: 0, serveBody: !HttpMethods.IsHead(request.Method));
-        }
-
-        private static void SetContentType(HttpContext httpContext, FileResult result)
-        {
-            var response = httpContext.Response;
-            response.ContentType = result.ContentType;
-        }
-
-        private static void SetContentDispositionHeader(HttpContext httpContext, FileResult result)
-        {
-            if (!string.IsNullOrEmpty(result.FileDownloadName))
-            {
-                // From RFC 2183, Sec. 2.3:
-                // The sender may want to suggest a filename to be used if the entity is
-                // detached and stored in a separate file. If the receiving MUA writes
-                // the entity to a file, the suggested filename should be used as a
-                // basis for the actual filename, where possible.
-                var contentDisposition = new ContentDispositionHeaderValue("attachment");
-                contentDisposition.SetHttpFileName(result.FileDownloadName);
-                httpContext.Response.Headers.ContentDisposition = contentDisposition.ToString();
-            }
-        }
-
-        private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset? lastModified, EntityTagHeaderValue? etag)
-        {
-            var httpResponseHeaders = response.GetTypedHeaders();
-            if (lastModified.HasValue)
-            {
-                httpResponseHeaders.LastModified = lastModified;
-            }
-            if (etag != null)
-            {
-                httpResponseHeaders.ETag = etag;
-            }
-        }
-
-        private static void SetAcceptRangeHeader(HttpResponse response)
-        {
-            response.Headers.AcceptRanges = AcceptRangeHeaderValue;
-        }
-
-        internal static bool IfRangeValid(
-            RequestHeaders httpRequestHeaders,
-            DateTimeOffset? lastModified,
-            EntityTagHeaderValue? etag,
-            ILogger logger)
-        {
-            // 14.27 If-Range
-            var ifRange = httpRequestHeaders.IfRange;
-            if (ifRange != null)
-            {
-                // If the validator given in the If-Range header field matches the
-                // current validator for the selected representation of the target
-                // resource, then the server SHOULD process the Range header field as
-                // requested.  If the validator does not match, the server MUST ignore
-                // the Range header field.
-                if (ifRange.LastModified.HasValue)
-                {
-                    if (lastModified.HasValue && lastModified > ifRange.LastModified)
-                    {
-                        logger.IfRangeLastModifiedPreconditionFailed(lastModified, ifRange.LastModified);
-                        return false;
-                    }
-                }
-                else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true))
-                {
-                    logger.IfRangeETagPreconditionFailed(etag, ifRange.EntityTag);
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        // Internal for testing
-        internal static PreconditionState GetPreconditionState(
-            RequestHeaders httpRequestHeaders,
-            DateTimeOffset? lastModified,
-            EntityTagHeaderValue? etag,
-            ILogger logger)
-        {
-            var ifMatchState = PreconditionState.Unspecified;
-            var ifNoneMatchState = PreconditionState.Unspecified;
-            var ifModifiedSinceState = PreconditionState.Unspecified;
-            var ifUnmodifiedSinceState = PreconditionState.Unspecified;
-
-            // 14.24 If-Match
-            var ifMatch = httpRequestHeaders.IfMatch;
-            if (etag != null)
-            {
-                ifMatchState = GetEtagMatchState(
-                    useStrongComparison: true,
-                    etagHeader: ifMatch,
-                    etag: etag,
-                    matchFoundState: PreconditionState.ShouldProcess,
-                    matchNotFoundState: PreconditionState.PreconditionFailed);
-
-                if (ifMatchState == PreconditionState.PreconditionFailed)
-                {
-                    logger.IfMatchPreconditionFailed(etag);
-                }
-            }
-
-            // 14.26 If-None-Match
-            var ifNoneMatch = httpRequestHeaders.IfNoneMatch;
-            if (etag != null)
-            {
-                ifNoneMatchState = GetEtagMatchState(
-                    useStrongComparison: false,
-                    etagHeader: ifNoneMatch,
-                    etag: etag,
-                    matchFoundState: PreconditionState.NotModified,
-                    matchNotFoundState: PreconditionState.ShouldProcess);
-            }
-
-            var now = RoundDownToWholeSeconds(DateTimeOffset.UtcNow);
-
-            // 14.25 If-Modified-Since
-            var ifModifiedSince = httpRequestHeaders.IfModifiedSince;
-            if (lastModified.HasValue && ifModifiedSince.HasValue && ifModifiedSince <= now)
-            {
-                var modified = ifModifiedSince < lastModified;
-                ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
-            }
-
-            // 14.28 If-Unmodified-Since
-            var ifUnmodifiedSince = httpRequestHeaders.IfUnmodifiedSince;
-            if (lastModified.HasValue && ifUnmodifiedSince.HasValue && ifUnmodifiedSince <= now)
-            {
-                var unmodified = ifUnmodifiedSince >= lastModified;
-                ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
-
-                if (ifUnmodifiedSinceState == PreconditionState.PreconditionFailed)
-                {
-                    logger.IfUnmodifiedSincePreconditionFailed(lastModified, ifUnmodifiedSince);
-                }
-            }
-
-            var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState);
-            return state;
-        }
-
-        private static PreconditionState GetEtagMatchState(
-            bool useStrongComparison,
-            IList<EntityTagHeaderValue> etagHeader,
-            EntityTagHeaderValue etag,
-            PreconditionState matchFoundState,
-            PreconditionState matchNotFoundState)
-        {
-            if (etagHeader?.Count > 0)
+            var fileResultInfo = new FileResultInfo
             {
-                var state = matchNotFoundState;
-                foreach (var entityTag in etagHeader)
-                {
-                    if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison))
-                    {
-                        state = matchFoundState;
-                        break;
-                    }
-                }
-
-                return state;
-            }
+                ContentType = result.ContentType,
+                EnableRangeProcessing = result.EnableRangeProcessing,
+                EntityTag = result.EntityTag,
+                FileDownloadName = result.FileDownloadName,
+                LastModified = result.LastModified,
+            };
 
-            return PreconditionState.Unspecified;
-        }
-
-        private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
-        {
-            var max = PreconditionState.Unspecified;
-            for (var i = 0; i < states.Length; i++)
-            {
-                if (states[i] > max)
-                {
-                    max = states[i];
-                }
-            }
-
-            return max;
-        }
-
-        private static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetRangeHeaders(
-            HttpContext httpContext,
-            RequestHeaders httpRequestHeaders,
-            long fileLength,
-            ILogger logger)
-        {
-            var response = httpContext.Response;
-            var httpResponseHeaders = response.GetTypedHeaders();
-            var serveBody = !HttpMethods.IsHead(httpContext.Request.Method);
-
-            // Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges 
-            // and when the file length is zero.
-            var (isRangeRequest, range) = RangeHelper.ParseRange(
-                httpContext,
-                httpRequestHeaders,
-                fileLength,
-                logger);
-
-            if (!isRangeRequest)
-            {
-                return (range: null, rangeLength: 0, serveBody);
-            }
-
-            // Requested range is not satisfiable
-            if (range == null)
-            {
-                // 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
-                // SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
-                // the current length of the selected resource.  e.g. */length
-                response.StatusCode = StatusCodes.Status416RangeNotSatisfiable;
-                httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(fileLength);
-                response.ContentLength = 0;
-
-                return (range: null, rangeLength: 0, serveBody: false);
-            }
-
-            response.StatusCode = StatusCodes.Status206PartialContent;
-            httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(
-                range.From!.Value,
-                range.To!.Value,
-                fileLength);
-
-            // Overwrite the Content-Length header for valid range requests with the range length.
-            var rangeLength = SetContentLength(response, range);
-
-            return (range, rangeLength, serveBody);
-        }
-
-        private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range)
-        {
-            var start = range.From!.Value;
-            var end = range.To!.Value;
-            var length = end - start + 1;
-            response.ContentLength = length;
-            return length;
+            return FileResultHelper.SetHeadersAndLog(context.HttpContext, fileResultInfo, fileLength, enableRangeProcessing, lastModified, etag, Logger);
         }
 
         /// <summary>
@@ -420,39 +99,7 @@ protected static ILogger CreateLogger<T>(ILoggerFactory factory)
         /// <returns>The async task.</returns>
         protected static async Task WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue? range, long rangeLength)
         {
-            await WriteFileAsyncInternal(context, fileStream, range, rangeLength);
-        }
-
-        internal static async Task WriteFileAsyncInternal(HttpContext context, Stream fileStream, RangeItemHeaderValue? range, long rangeLength)
-        {
-            var outputStream = context.Response.Body;
-            using (fileStream)
-            {
-                try
-                {
-                    if (range == null)
-                    {
-                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count: null, bufferSize: BufferSize, cancel: context.RequestAborted);
-                    }
-                    else
-                    {
-                        fileStream.Seek(range.From!.Value, SeekOrigin.Begin);
-                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, rangeLength, BufferSize, context.RequestAborted);
-                    }
-                }
-                catch (OperationCanceledException)
-                {
-                    // Don't throw this exception, it's most likely caused by the client disconnecting.
-                    // However, if it was cancelled for any other reason we need to prevent empty responses.
-                    context.Abort();
-                }
-            }
-        }
-
-        private static DateTimeOffset RoundDownToWholeSeconds(DateTimeOffset dateTimeOffset)
-        {
-            var ticksToRemove = dateTimeOffset.Ticks % TimeSpan.TicksPerSecond;
-            return dateTimeOffset.Subtract(TimeSpan.FromTicks(ticksToRemove));
+            await FileResultHelper.WriteFileAsync(context, fileStream, range, rangeLength);
         }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs b/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
index 5c7c6e8b5b7e..b8d202148e6e 100644
--- a/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
+++ b/src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs
@@ -7,6 +7,7 @@
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Core;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Extensions.Logging;
@@ -55,7 +56,8 @@ public async Task ExecuteAsync(ActionContext context, JsonResult result)
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 result.ContentType,
                 response.ContentType,
-                DefaultContentType,
+                (DefaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.Core/src/JsonResult.cs b/src/Mvc/Mvc.Core/src/JsonResult.cs
index a31e55e61d3f..e752b995a7b5 100644
--- a/src/Mvc/Mvc.Core/src/JsonResult.cs
+++ b/src/Mvc/Mvc.Core/src/JsonResult.cs
@@ -4,7 +4,6 @@
 using System;
 using System.Text.Json;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.Extensions.DependencyInjection;
 
@@ -13,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// <summary>
     /// An action result which formats the given object as JSON.
     /// </summary>
-    public class JsonResult : ActionResult, IResult, IStatusCodeActionResult
+    public class JsonResult : ActionResult, IStatusCodeActionResult
     {
         /// <summary>
         /// Creates a new <see cref="JsonResult"/> with the given <paramref name="value"/>.
@@ -81,15 +80,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        /// <summary>
-        /// Write the result as JSON to the HTTP response.
-        /// </summary>
-        /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
-        /// <returns>A task that represents the asynchronous execute operation.</returns>
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            return httpContext.Response.WriteAsJsonAsync(Value);
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj
index 745f799848f5..28887a6351a8 100644
--- a/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj
+++ b/src/Mvc/Mvc.Core/src/Microsoft.AspNetCore.Mvc.Core.csproj
@@ -26,6 +26,8 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
     <Compile Include="$(SharedSourceRoot)PropertyHelper\**\*.cs" />
     <Compile Include="$(SharedSourceRoot)RangeHelper\**\*.cs" />
     <Compile Include="$(SharedSourceRoot)SecurityHelper\**\*.cs" />
+    <Compile Include="$(SharedSourceRoot)ResponseContentTypeHelper.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)ResultsHelpers\*.cs" LinkBase="Shared" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs b/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs
index 8c3a0a27651f..d510b5f737be 100644
--- a/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs
+++ b/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs
@@ -322,7 +322,7 @@ static MvcCoreLoggerExtensions()
             _httpStatusCodeResultExecuting = LoggerMessage.Define<int>(
                 LogLevel.Information,
                 new EventId(1, "HttpStatusCodeResultExecuting"),
-                "Executing HttpStatusCodeResult, setting HTTP status code {StatusCode}");
+                "Executing StatusCodeResult, setting HTTP status code {StatusCode}");
 
             _localRedirectResultExecuting = LoggerMessage.Define<string>(
                 LogLevel.Information,
diff --git a/src/Mvc/Mvc.Core/src/PhysicalFileResult.cs b/src/Mvc/Mvc.Core/src/PhysicalFileResult.cs
index 9734ddbacdfe..def6a2b65408 100644
--- a/src/Mvc/Mvc.Core/src/PhysicalFileResult.cs
+++ b/src/Mvc/Mvc.Core/src/PhysicalFileResult.cs
@@ -3,13 +3,9 @@
 
 using System;
 using System.Diagnostics.CodeAnalysis;
-using System.IO;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Core;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
 using Microsoft.Net.Http.Headers;
 
 namespace Microsoft.AspNetCore.Mvc
@@ -18,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// A <see cref="FileResult"/> on execution will write a file from disk to the response
     /// using mechanisms provided by the host.
     /// </summary>
-    public class PhysicalFileResult : FileResult, IResult
+    public class PhysicalFileResult : FileResult
     {
         private string _fileName;
 
@@ -70,51 +66,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<PhysicalFileResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-
-            var fileInfo = new FileInfo(FileName);
-            if (!fileInfo.Exists)
-            {
-                throw new FileNotFoundException(
-                    Resources.FormatFileResult_InvalidPath(FileName), FileName);
-            }
-
-            return ExecuteAsyncInternal(httpContext, this, fileInfo.LastWriteTimeUtc, fileInfo.Length);
-        }
-
-        internal static Task ExecuteAsyncInternal(
-            HttpContext httpContext,
-            PhysicalFileResult result,
-            DateTimeOffset fileLastModified,
-            long fileLength)
-        {
-            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
-            var logger = loggerFactory.CreateLogger<RedirectResult>();
-
-            logger.ExecutingFileResult(result, result.FileName);
-
-            var lastModified = result.LastModified ?? fileLastModified;
-            var (range, rangeLength, serveBody) = FileResultExecutorBase.SetHeadersAndLog(
-                httpContext,
-                result,
-                fileLength,
-                result.EnableRangeProcessing,
-                lastModified,
-                result.EntityTag,
-                logger);
-
-            if (serveBody)
-            {
-                return PhysicalFileResultExecutor.WriteFileAsyncInternal(httpContext, result, range, rangeLength, logger);
-            }
-
-            return Task.CompletedTask;
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/RedirectResult.cs b/src/Mvc/Mvc.Core/src/RedirectResult.cs
index 8ce2beb6f6b4..5077bf9285e4 100644
--- a/src/Mvc/Mvc.Core/src/RedirectResult.cs
+++ b/src/Mvc/Mvc.Core/src/RedirectResult.cs
@@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// An <see cref="ActionResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
     /// or Permanent Redirect (308) response with a Location header to the supplied URL.
     /// </summary>
-    public class RedirectResult : ActionResult, IResult, IKeepTempDataResult
+    public class RedirectResult : ActionResult, IKeepTempDataResult
     {
         private string _url;
 
@@ -115,36 +115,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<RedirectResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        /// <inheritdoc />
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-
-            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
-            var logger = loggerFactory.CreateLogger<RedirectResult>();
-
-            // IsLocalUrl is called to handle URLs starting with '~/'.
-            var destinationUrl = UrlHelperBase.CheckIsLocalUrl(_url) ? UrlHelperBase.Content(httpContext, _url) : _url;
-
-            logger.RedirectResultExecuting(destinationUrl);
-
-            if (PreserveMethod)
-            {
-                httpContext.Response.StatusCode = Permanent
-                    ? StatusCodes.Status308PermanentRedirect
-                    : StatusCodes.Status307TemporaryRedirect;
-                httpContext.Response.Headers.Location = destinationUrl;
-            }
-            else
-            {
-                httpContext.Response.Redirect(destinationUrl, Permanent);
-            }
-
-            return Task.CompletedTask;
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/src/SignInResult.cs b/src/Mvc/Mvc.Core/src/SignInResult.cs
index d890868a1dfd..185afa671680 100644
--- a/src/Mvc/Mvc.Core/src/SignInResult.cs
+++ b/src/Mvc/Mvc.Core/src/SignInResult.cs
@@ -5,8 +5,6 @@
 using System.Security.Claims;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Core;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 
@@ -15,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// <summary>
     /// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.SignInAsync"/>.
     /// </summary>
-    public class SignInResult : ActionResult, IResult
+    public class SignInResult : ActionResult
     {
         /// <summary>
         /// Initializes a new instance of <see cref="SignInResult"/> with the
@@ -86,17 +84,7 @@ public override Task ExecuteResultAsync(ActionContext context)
                 throw new ArgumentNullException(nameof(context));
             }
 
-            return ExecuteAsync(context.HttpContext);
-        }
-
-        /// <inheritdoc />
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            return ExecuteAsync(httpContext);
-        }
-
-        private Task ExecuteAsync(HttpContext httpContext)
-        {
+            var httpContext = context.HttpContext;
             var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
             var logger = loggerFactory.CreateLogger<SignInResult>();
 
diff --git a/src/Mvc/Mvc.Core/src/VirtualFileResult.cs b/src/Mvc/Mvc.Core/src/VirtualFileResult.cs
index ac95c84a07eb..ff83cbc74d04 100644
--- a/src/Mvc/Mvc.Core/src/VirtualFileResult.cs
+++ b/src/Mvc/Mvc.Core/src/VirtualFileResult.cs
@@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
     /// A <see cref="FileResult" /> that on execution writes the file specified using a virtual path to the response
     /// using mechanisms provided by the host.
     /// </summary>
-    public class VirtualFileResult : FileResult, IResult
+    public class VirtualFileResult : FileResult
     {
         private string _fileName;
 
@@ -74,42 +74,5 @@ public override Task ExecuteResultAsync(ActionContext context)
             var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<VirtualFileResult>>();
             return executor.ExecuteAsync(context, this);
         }
-
-        Task IResult.ExecuteAsync(HttpContext httpContext)
-        {
-            if (httpContext == null)
-            {
-                throw new ArgumentNullException(nameof(httpContext));
-            }
-
-            var hostingEnvironment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
-
-            var fileInfo = VirtualFileResultExecutor.GetFileInformation(this, hostingEnvironment);
-            if (!fileInfo.Exists)
-            {
-                throw new FileNotFoundException(
-                    Resources.FormatFileResult_InvalidPath(FileName), FileName);
-            }
-
-            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
-            var logger = loggerFactory.CreateLogger<RedirectResult>();
-
-            var lastModified = LastModified ?? fileInfo.LastModified;
-            var (range, rangeLength, serveBody) = FileResultExecutorBase.SetHeadersAndLog(
-                httpContext,
-                this,
-                fileInfo.Length,
-                EnableRangeProcessing,
-                lastModified,
-                EntityTag,
-                logger);
-
-            if (serveBody)
-            {
-                return VirtualFileResultExecutor.WriteFileAsyncInternal(httpContext, fileInfo, range, rangeLength, logger);
-            }
-
-            return Task.CompletedTask;
-        }
     }
 }
diff --git a/src/Mvc/Mvc.Core/test/AcceptedResultTests.cs b/src/Mvc/Mvc.Core/test/AcceptedResultTests.cs
index da714c3f50dc..4dcb7fadd072 100644
--- a/src/Mvc/Mvc.Core/test/AcceptedResultTests.cs
+++ b/src/Mvc/Mvc.Core/test/AcceptedResultTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
@@ -14,7 +14,7 @@
 using Moq;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc.Core.Test
+namespace Microsoft.AspNetCore.Mvc
 {
     public class AcceptedResultTests
     {
diff --git a/src/Mvc/Mvc.Core/test/ChallengeResultTest.cs b/src/Mvc/Mvc.Core/test/ChallengeResultTest.cs
index d36fdef0cb4f..2de96e8c8c9b 100644
--- a/src/Mvc/Mvc.Core/test/ChallengeResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/ChallengeResultTest.cs
@@ -67,43 +67,6 @@ public async Task ChallengeResult_ExecuteResultAsync_NoSchemes()
             auth.Verify(c => c.ChallengeAsync(httpContext.Object, null, null), Times.Exactly(1));
         }
 
-        [Fact]
-        public async Task ChallengeResult_ExecuteAsync()
-        {
-            // Arrange
-            var result = new ChallengeResult("", null);
-
-            var auth = new Mock<IAuthenticationService>();
-
-            var httpContext = new Mock<HttpContext>();
-            httpContext.SetupGet(c => c.RequestServices)
-                .Returns(CreateServices().AddSingleton(auth.Object).BuildServiceProvider());
-
-            // Act
-            await ((IResult)result).ExecuteAsync(httpContext.Object);
-
-            // Assert
-            auth.Verify(c => c.ChallengeAsync(httpContext.Object, "", null), Times.Exactly(1));
-        }
-
-        [Fact]
-        public async Task ChallengeResult_ExecuteAsync_NoSchemes()
-        {
-            // Arrange
-            var result = new ChallengeResult(new string[] { }, null);
-
-            var auth = new Mock<IAuthenticationService>();
-            var httpContext = new Mock<HttpContext>();
-            httpContext.SetupGet(c => c.RequestServices)
-                .Returns(CreateServices().AddSingleton(auth.Object).BuildServiceProvider());
-
-            // Act
-            await ((IResult)result).ExecuteAsync(httpContext.Object);
-
-            // Assert
-            auth.Verify(c => c.ChallengeAsync(httpContext.Object, null, null), Times.Exactly(1));
-        }
-
         private static IServiceCollection CreateServices()
         {
             var services = new ServiceCollection();
diff --git a/src/Mvc/Mvc.Core/test/ContentResultTest.cs b/src/Mvc/Mvc.Core/test/ContentResultTest.cs
index db4e0c66c803..ebc7e7b34578 100644
--- a/src/Mvc/Mvc.Core/test/ContentResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/ContentResultTest.cs
@@ -45,28 +45,6 @@ public async Task ContentResult_ExecuteResultAsync_Response_NullContent_SetsCont
             MediaTypeAssert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
         }
 
-        [Fact]
-        public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
-        {
-            // Arrange
-            var contentResult = new ContentResult
-            {
-                Content = null,
-                ContentType = new MediaTypeHeaderValue("text/plain")
-                {
-                    Encoding = Encoding.Unicode
-                }.ToString()
-            };
-            var httpContext = GetHttpContext();
-            var actionContext = GetActionContext(httpContext);
-
-            // Act
-            await ((IResult)contentResult).ExecuteAsync(httpContext);
-
-            // Assert
-            MediaTypeAssert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
-        }
-
         public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
         {
             get
@@ -165,36 +143,6 @@ public async Task ContentResult_ExecuteResultAsync_SetContentTypeAndEncoding_OnR
             Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
         }
 
-        [Theory]
-        [MemberData(nameof(ContentResultContentTypeData))]
-        public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnResponse(
-           MediaTypeHeaderValue contentType,
-           string content,
-           string responseContentType,
-           string expectedContentType,
-           byte[] expectedContentData)
-        {
-            // Arrange
-            var contentResult = new ContentResult
-            {
-                Content = content,
-                ContentType = contentType?.ToString()
-            };
-            var httpContext = GetHttpContext();
-            var memoryStream = new MemoryStream();
-            httpContext.Response.Body = memoryStream;
-            httpContext.Response.ContentType = responseContentType;
-
-            // Act
-            await ((IResult)contentResult).ExecuteAsync(httpContext);
-
-            // Assert
-            var finalResponseContentType = httpContext.Response.ContentType;
-            Assert.Equal(expectedContentType, finalResponseContentType);
-            Assert.Equal(expectedContentData, memoryStream.ToArray());
-            Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
-        }
-
         public static TheoryData<string, string> ContentResult_WritesDataCorrectly_ForDifferentContentSizesData
         {
             get
@@ -298,31 +246,6 @@ public async Task ContentResult_WritesDataCorrectly_ForDifferentContentSizes(str
             Assert.Equal(content, actualContent);
         }
 
-        [Theory]
-        [MemberData(nameof(ContentResult_WritesDataCorrectly_ForDifferentContentSizesData))]
-        public async Task ContentResult_ExecuteAsync_WritesDataCorrectly_ForDifferentContentSizes(string content, string contentType)
-        {
-            // Arrange
-            var contentResult = new ContentResult
-            {
-                Content = content,
-                ContentType = contentType
-            };
-            var httpContext = GetHttpContext();
-            var memoryStream = new MemoryStream();
-            httpContext.Response.Body = memoryStream;
-            var encoding = MediaTypeHeaderValue.Parse(contentType).Encoding;
-
-            // Act
-            await ((IResult)contentResult).ExecuteAsync(httpContext);
-
-            // Assert
-            memoryStream.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(memoryStream, encoding);
-            var actualContent = await streamReader.ReadToEndAsync();
-            Assert.Equal(content, actualContent);
-        }
-
         private static ActionContext GetActionContext(HttpContext httpContext)
         {
             var routeData = new RouteData();
diff --git a/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs b/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs
index e29e773bbaba..f4cf6bf3e2f1 100644
--- a/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs
+++ b/src/Mvc/Mvc.Core/test/ControllerBaseTest.cs
@@ -10,7 +10,6 @@
 using System.Text;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Abstractions;
 using Microsoft.AspNetCore.Mvc.Controllers;
 using Microsoft.AspNetCore.Mvc.DataAnnotations;
 using Microsoft.AspNetCore.Mvc.Formatters;
@@ -27,7 +26,7 @@
 using Moq;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc.Core.Test
+namespace Microsoft.AspNetCore.Mvc
 {
     public class ControllerBaseTest
     {
diff --git a/src/Mvc/Mvc.Core/test/FileContentActionResultTest.cs b/src/Mvc/Mvc.Core/test/FileContentActionResultTest.cs
deleted file mode 100644
index a8e91fddd761..000000000000
--- a/src/Mvc/Mvc.Core/test/FileContentActionResultTest.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.Net.Http.Headers;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc
-{
-    public class FileContentActionResultTest
-    {
-        [Fact]
-        public void Constructor_SetsFileContents()
-        {
-            // Arrange
-            var fileContents = new byte[0];
-
-            // Act
-            var result = new FileContentResult(fileContents, "text/plain");
-
-            // Assert
-            Assert.Same(fileContents, result.FileContents);
-        }
-
-        [Fact]
-        public void Constructor_SetsContentTypeAndParameters()
-        {
-            // Arrange
-            var fileContents = new byte[0];
-            var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var expectedMediaType = contentType;
-
-            // Act
-            var result = new FileContentResult(fileContents, contentType);
-
-            // Assert
-            Assert.Same(fileContents, result.FileContents);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
-        }
-
-        [Fact]
-        public void Constructor_SetsLastModifiedAndEtag()
-        {
-            // Arrange
-            var fileContents = new byte[0];
-            var contentType = "text/plain";
-            var expectedMediaType = contentType;
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-
-            // Act
-            var result = new FileContentResult(fileContents, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag
-            };
-
-            // Assert
-            Assert.Equal(lastModified, result.LastModified);
-            Assert.Equal(entityTag, result.EntityTag);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_CopiesBuffer_ToOutputStream()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_CopiesBuffer_ToOutputStream(action);
-        }
-
-        [Theory]
-        [InlineData(0, 4, "Hello", 5)]
-        [InlineData(6, 10, "World", 5)]
-        [InlineData(null, 5, "World", 5)]
-        [InlineData(6, null, "World", 5)]
-        public async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest
-                .WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(start, end, expectedString, contentLength, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 12-13")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(rangeString, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionFailed_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_NotModified_RangeRequestedIgnored()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_NotModified_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<FileContentResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseFileContentResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
-        }
-    }
-}
diff --git a/src/Mvc/Mvc.Core/test/FileContentResult.cs b/src/Mvc/Mvc.Core/test/FileContentResult.cs
deleted file mode 100644
index 7be596c7ee6a..000000000000
--- a/src/Mvc/Mvc.Core/test/FileContentResult.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc
-{
-    public class FileContentResultTest
-    {
-        [Fact]
-        public async Task WriteFileAsync_CopiesBuffer_ToOutputStream()
-        {var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-            await BaseFileContentResultTest.WriteFileAsync_CopiesBuffer_ToOutputStream(action);
-        }
-
-        [Theory]
-        [InlineData(0, 4, "Hello", 5)]
-        [InlineData(6, 10, "World", 5)]
-        [InlineData(null, 5, "World", 5)]
-        [InlineData(6, null, "World", 5)]
-        public async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest
-                .WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(start, end, expectedString, contentLength, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 12-13")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(rangeString, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_PreconditionFailed_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_NotModified_RangeRequestedIgnored()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.WriteFileAsync_NotModified_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<FileContentResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseFileContentResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
-        }
-    }
-}
diff --git a/src/Mvc/Mvc.Core/test/FileContentResultTest.cs b/src/Mvc/Mvc.Core/test/FileContentResultTest.cs
new file mode 100644
index 000000000000..5012dffa2518
--- /dev/null
+++ b/src/Mvc/Mvc.Core/test/FileContentResultTest.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+    public class FileContentResultTest : FileContentResultTestBase
+    {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            byte[] buffer,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            var result = new FileContentResult(buffer, contentType)
+            {
+                EntityTag = entityTag,
+                LastModified = lastModified,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+            httpContext.RequestServices = new ServiceCollection()
+                .AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
+                .AddSingleton<IActionResultExecutor<FileContentResult>, FileContentResultExecutor>()
+                .BuildServiceProvider();
+            var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
+
+            return result.ExecuteResultAsync(context);
+        }
+    }
+}
diff --git a/src/Mvc/Mvc.Core/test/FileResultTest.cs b/src/Mvc/Mvc.Core/test/FileResultHelperTest.cs
similarity index 97%
rename from src/Mvc/Mvc.Core/test/FileResultTest.cs
rename to src/Mvc/Mvc.Core/test/FileResultHelperTest.cs
index 25e6d6f3634e..69b94ab7f7cb 100644
--- a/src/Mvc/Mvc.Core/test/FileResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/FileResultHelperTest.cs
@@ -5,15 +5,14 @@
 using System.IO;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Abstractions;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.AspNetCore.Routing;
-using Microsoft.AspNetCore.Testing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 using Microsoft.Net.Http.Headers;
-using Moq;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Mvc
@@ -270,14 +269,14 @@ public void GetPreconditionState_ShouldProcess(string ifMatch, string ifNoneMatc
             actionContext.HttpContext = httpContext;
 
             // Act
-            var state = FileResultExecutorBase.GetPreconditionState(
+            var state = FileResultHelper.GetPreconditionState(
                 httpRequestHeaders,
                 lastModified,
                 etag,
                 NullLogger.Instance);
 
             // Assert
-            Assert.Equal(FileResultExecutorBase.PreconditionState.ShouldProcess, state);
+            Assert.Equal(FileResultHelper.PreconditionState.ShouldProcess, state);
         }
 
         [Theory]
@@ -309,14 +308,14 @@ public void GetPreconditionState_ShouldNotProcess_PreconditionFailed(string ifMa
             actionContext.HttpContext = httpContext;
 
             // Act
-            var state = FileResultExecutorBase.GetPreconditionState(
+            var state = FileResultHelper.GetPreconditionState(
                 httpRequestHeaders,
                 lastModified,
                 etag,
                 NullLogger.Instance);
 
             // Assert
-            Assert.Equal(FileResultExecutorBase.PreconditionState.PreconditionFailed, state);
+            Assert.Equal(FileResultHelper.PreconditionState.PreconditionFailed, state);
         }
 
         [Theory]
@@ -346,14 +345,14 @@ public void GetPreconditionState_ShouldNotProcess_NotModified(string ifMatch, st
             actionContext.HttpContext = httpContext;
 
             // Act
-            var state = FileResultExecutorBase.GetPreconditionState(
+            var state = FileResultHelper.GetPreconditionState(
                 httpRequestHeaders,
                 lastModified,
                 etag,
                 NullLogger.Instance);
 
             // Assert
-            Assert.Equal(FileResultExecutorBase.PreconditionState.NotModified, state);
+            Assert.Equal(FileResultHelper.PreconditionState.NotModified, state);
         }
 
         [Theory]
@@ -374,7 +373,7 @@ public void IfRangeValid_IgnoreRangeRequest(string ifRangeString, bool expected)
             actionContext.HttpContext = httpContext;
 
             // Act
-            var ifRangeIsValid = FileResultExecutorBase.IfRangeValid(
+            var ifRangeIsValid = FileResultHelper.IfRangeValid(
                 httpRequestHeaders,
                 lastModified,
                 etag,
diff --git a/src/Mvc/Mvc.Core/test/FileStreamResultTest.cs b/src/Mvc/Mvc.Core/test/FileStreamResultTest.cs
index fc40761013a3..e34e96c526fb 100644
--- a/src/Mvc/Mvc.Core/test/FileStreamResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/FileStreamResultTest.cs
@@ -3,25 +3,44 @@
 
 using System;
 using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Routing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 using Microsoft.Net.Http.Headers;
-using Moq;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Mvc
 {
-    public class FileStreamResultTest
+    public class FileStreamResultTest : FileStreamResultTestBase
     {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            Stream stream,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            httpContext.RequestServices = new ServiceCollection()
+                .AddSingleton<ILoggerFactory, NullLoggerFactory>()
+                .AddSingleton<IActionResultExecutor<FileStreamResult>, FileStreamResultExecutor>()
+                .BuildServiceProvider();
+
+            var actionContext = new ActionContext(httpContext, new(), new());
+            var fileStreamResult = new FileStreamResult(stream, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing
+            };
+
+            return fileStreamResult.ExecuteResultAsync(actionContext);
+        }
+
         [Fact]
         public void Constructor_SetsFileName()
         {
@@ -48,7 +67,7 @@ public void Constructor_SetsContentTypeAndParameters()
 
             // Assert
             Assert.Equal(stream, result.FileStream);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
+            Assert.Equal(expectedMediaType, result.ContentType);
         }
 
         [Fact]
@@ -71,558 +90,8 @@ public void Constructor_SetsLastModifiedAndEtag()
             // Assert
             Assert.Equal(lastModified, result.LastModified);
             Assert.Equal(entityTag, result.EntityTag);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
-        }
-
-        [Theory]
-        [InlineData(0, 4, "Hello", 5)]
-        [InlineData(6, 10, "World", 5)]
-        [InlineData(null, 5, "World", 5)]
-        [InlineData(6, null, "World", 5)]
-        public async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-            readStream.SetLength(11);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.Range = new RangeHeaderValue(start, end);
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            start = start ?? 11 - end;
-            end = start + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            var contentRange = new ContentRangeHeaderValue(start.Value, end.Value, byteArray.Length);
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
-            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
-            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
-            Assert.Equal(contentLength, httpResponse.ContentLength);
-            Assert.Equal(expectedString, body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = DateTimeOffset.MinValue;
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-            readStream.SetLength(11);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            requestHeaders.Range = new RangeHeaderValue(0, 4);
-            requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            var contentRange = new ContentRangeHeaderValue(0, 4, byteArray.Length);
-            Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
-            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
-            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
-            Assert.Equal(5, httpResponse.ContentLength);
-            Assert.Equal("Hello", body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = DateTimeOffset.MinValue;
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-            readStream.SetLength(11);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            requestHeaders.Range = new RangeHeaderValue(0, 4);
-            requestHeaders.IfRange = new RangeConditionHeaderValue(DateTimeOffset.MinValue);
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            Assert.Equal("Hello World", body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = DateTimeOffset.MinValue.AddDays(1);
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-            readStream.SetLength(11);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            requestHeaders.Range = new RangeHeaderValue(0, 4);
-            requestHeaders.IfRange = new RangeConditionHeaderValue(DateTimeOffset.MinValue);
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            Assert.Equal("Hello World", body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
-        {
-            // Arrange            
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            httpContext.Request.Headers.Range = rangeString;
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Empty(httpResponse.Headers.ContentRange);
-            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            Assert.Equal("Hello World", body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Theory]
-        [InlineData("bytes = 12-13")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            // Arrange            
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            httpContext.Request.Headers.Range = rangeString;
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            var contentRange = new ContentRangeHeaderValue(byteArray.Length);
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            Assert.Equal(StatusCodes.Status416RangeNotSatisfiable, httpResponse.StatusCode);
-            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
-            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
-            Assert.Equal(0, httpResponse.ContentLength);
-            Assert.Empty(body);
-            Assert.False(readStream.CanSeek);
+            Assert.Equal(expectedMediaType, result.ContentType);
         }
 
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"NotEtag\""),
-            };
-            httpContext.Request.Headers.Range = "bytes = 0-6";
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Equal(StatusCodes.Status412PreconditionFailed, httpResponse.StatusCode);
-            Assert.Null(httpResponse.ContentLength);
-            Assert.Empty(httpResponse.Headers.ContentRange);
-            Assert.NotEmpty(httpResponse.Headers.LastModified);
-            Assert.Empty(body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_NotModified_RangeRequestedIgnored()
-        {
-            // Arrange       
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("Hello World");
-            var readStream = new MemoryStream(byteArray);
-
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.IfNoneMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            httpContext.Request.Headers.Range = "bytes = 0-6";
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = await streamReader.ReadToEndAsync();
-            Assert.Equal(StatusCodes.Status304NotModified, httpResponse.StatusCode);
-            Assert.Null(httpResponse.ContentLength);
-            Assert.Empty(httpResponse.Headers.ContentRange);            
-            Assert.False(httpResponse.Headers.ContainsKey(HeaderNames.ContentType));
-            Assert.NotEmpty(httpResponse.Headers.LastModified);
-            Assert.Empty(body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Theory]
-        [InlineData(0)]
-        [InlineData(null)]
-        public async Task WriteFileAsync_RangeRequested_FileLengthZeroOrNull(long? fileLength)
-        {
-            // Arrange
-            var contentType = "text/plain";
-            var lastModified = new DateTimeOffset();
-            var entityTag = new EntityTagHeaderValue("\"Etag\"");
-            var byteArray = Encoding.ASCII.GetBytes("");
-            var readStream = new MemoryStream(byteArray);
-            fileLength = fileLength ?? 0L;
-            readStream.SetLength(fileLength.Value);
-            var result = new FileStreamResult(readStream, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
-            var httpContext = GetHttpContext();
-            var requestHeaders = httpContext.Request.GetTypedHeaders();
-            requestHeaders.Range = new RangeHeaderValue(0, 5);
-            requestHeaders.IfMatch = new[]
-            {
-                new EntityTagHeaderValue("\"Etag\""),
-            };
-            httpContext.Request.Method = HttpMethods.Get;
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var httpResponse = actionContext.HttpContext.Response;
-            httpResponse.Body.Seek(0, SeekOrigin.Begin);
-            var streamReader = new StreamReader(httpResponse.Body);
-            var body = streamReader.ReadToEndAsync().Result;
-            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
-            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
-            var contentRange = new ContentRangeHeaderValue(byteArray.Length);
-            Assert.Equal(StatusCodes.Status416RangeNotSatisfiable, httpResponse.StatusCode);
-            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
-            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
-            Assert.Equal(0, httpResponse.ContentLength);
-            Assert.Empty(body);
-            Assert.False(readStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_WritesResponse_InChunksOfFourKilobytes()
-        {
-            // Arrange
-            var mockReadStream = new Mock<Stream>();
-            mockReadStream.SetupSequence(s => s.ReadAsync(It.IsAny<byte[]>(), 0, 0x1000, CancellationToken.None))
-                .Returns(Task.FromResult(0x1000))
-                .Returns(Task.FromResult(0x500))
-                .Returns(Task.FromResult(0));
-
-            var mockBodyStream = new Mock<Stream>();
-            mockBodyStream
-                .Setup(s => s.WriteAsync(It.IsAny<byte[]>(), 0, 0x1000, CancellationToken.None))
-                .Returns(Task.FromResult(0));
-
-            mockBodyStream
-                .Setup(s => s.WriteAsync(It.IsAny<byte[]>(), 0, 0x500, CancellationToken.None))
-                .Returns(Task.FromResult(0));
-
-            var result = new FileStreamResult(mockReadStream.Object, "text/plain");
-
-            var httpContext = GetHttpContext();
-            httpContext.Response.Body = mockBodyStream.Object;
-
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            mockReadStream.Verify();
-            mockBodyStream.Verify();
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_CopiesProvidedStream_ToOutputStream()
-        {
-            // Arrange
-            // Generate an array of bytes with a predictable pattern
-            // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13
-            var originalBytes = Enumerable.Range(0, 0x1234)
-                .Select(b => (byte)(b % 20)).ToArray();
-
-            var originalStream = new MemoryStream(originalBytes);
-
-            var httpContext = GetHttpContext();
-            var outStream = new MemoryStream();
-            httpContext.Response.Body = outStream;
-
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            var result = new FileStreamResult(originalStream, "text/plain");
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var outBytes = outStream.ToArray();
-            Assert.True(originalBytes.SequenceEqual(outBytes));
-            Assert.False(originalStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task SetsSuppliedContentTypeAndEncoding()
-        {
-            // Arrange
-            var expectedContentType = "text/foo; charset=us-ascii";
-            // Generate an array of bytes with a predictable pattern
-            // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13
-            var originalBytes = Enumerable.Range(0, 0x1234)
-                .Select(b => (byte)(b % 20)).ToArray();
-
-            var originalStream = new MemoryStream(originalBytes);
-
-            var httpContext = GetHttpContext();
-            var outStream = new MemoryStream();
-            httpContext.Response.Body = outStream;
-
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            var result = new FileStreamResult(originalStream, expectedContentType);
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            var outBytes = outStream.ToArray();
-            Assert.True(originalBytes.SequenceEqual(outBytes));
-            Assert.Equal(expectedContentType, httpContext.Response.ContentType);
-            Assert.False(originalStream.CanSeek);
-        }
-
-        [Fact]
-        public async Task HeadRequest_DoesNotWriteToBody_AndClosesReadStream()
-        {
-            // Arrange
-            var readStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello, World!"));
-
-            var httpContext = GetHttpContext();
-            httpContext.Request.Method = "HEAD";
-            var outStream = new MemoryStream();
-            httpContext.Response.Body = outStream;
-
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            var result = new FileStreamResult(readStream, "text/plain");
-
-            // Act
-            await result.ExecuteResultAsync(actionContext);
-
-            // Assert
-            Assert.False(readStream.CanSeek);
-            Assert.Equal(200, httpContext.Response.StatusCode);
-            Assert.Equal(0, httpContext.Response.Body.Length);
-        }
-
-        private static IServiceCollection CreateServices()
-        {
-            var services = new ServiceCollection();
-            services.AddSingleton<IActionResultExecutor<FileStreamResult>, FileStreamResultExecutor>();
-            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
-            return services;
-        }
-
-        private static HttpContext GetHttpContext()
-        {
-            var services = CreateServices();
-
-            var httpContext = new DefaultHttpContext();
-            httpContext.RequestServices = services.BuildServiceProvider();
-            return httpContext;
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/src/Mvc/Mvc.Core/test/Formatters/ResponseContentTypeHelperTest.cs b/src/Mvc/Mvc.Core/test/Formatters/ResponseContentTypeHelperTest.cs
index 6895f5528822..362278f86409 100644
--- a/src/Mvc/Mvc.Core/test/Formatters/ResponseContentTypeHelperTest.cs
+++ b/src/Mvc/Mvc.Core/test/Formatters/ResponseContentTypeHelperTest.cs
@@ -2,10 +2,11 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System.Text;
+using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Net.Http.Headers;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc.Formatters
+namespace Microsoft.AspNetCore.Internal
 {
     public class ResponseContentTypeHelperTest
     {
@@ -108,7 +109,8 @@ public void GetsExpectedContentTypeAndEncoding(
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 contentType?.ToString(),
                 responseContentType,
-                defaultContentType,
+                (defaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
@@ -127,7 +129,8 @@ public void DoesNotThrowException_OnInvalidResponseContentType()
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 null,
                 expectedContentType,
-                defaultContentType,
+                (defaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.Core/test/Microsoft.AspNetCore.Mvc.Core.Test.csproj b/src/Mvc/Mvc.Core/test/Microsoft.AspNetCore.Mvc.Core.Test.csproj
index bad827b0182f..e8aa8f9575e3 100644
--- a/src/Mvc/Mvc.Core/test/Microsoft.AspNetCore.Mvc.Core.Test.csproj
+++ b/src/Mvc/Mvc.Core/test/Microsoft.AspNetCore.Mvc.Core.Test.csproj
@@ -15,5 +15,6 @@
     <ProjectReference Include="..\..\shared\Mvc.TestDiagnosticListener\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj" />
 
     <Reference Include="Microsoft.AspNetCore.ResponseCaching" />
+    <Compile Include="$(SharedSourceRoot)ResultsTests\*.cs" LinkBase="Shared" />
   </ItemGroup>
 </Project>
diff --git a/src/Mvc/Mvc.Core/test/PhysicalFileActionResultTest.cs b/src/Mvc/Mvc.Core/test/PhysicalFileActionResultTest.cs
deleted file mode 100644
index 7677af32f434..000000000000
--- a/src/Mvc/Mvc.Core/test/PhysicalFileActionResultTest.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc
-{
-    public class PhysicalFileActionResultTest
-    {
-        [Fact]
-        public void Constructor_SetsFileName()
-        {
-            // Arrange
-            var path = Path.GetFullPath("helllo.txt");
-
-            // Act
-            var result = new PhysicalFileResult(path, "text/plain");
-
-            // Assert
-            Assert.Equal(path, result.FileName);
-        }
-
-        [Fact]
-        public void Constructor_SetsContentTypeAndParameters()
-        {
-            // Arrange
-            var path = Path.GetFullPath("helllo.txt");
-            var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var expectedMediaType = contentType;
-
-            // Act
-            var result = new PhysicalFileResult(path, contentType);
-
-            // Assert
-            Assert.Equal(path, result.FileName);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
-        }
-
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 5, "ts�", 5)]
-        [InlineData(8, null, "ResultTestFile contents�", 26)]
-        public async Task WriteFileAsync_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_WritesRangeRequested(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 35-36")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequestedNotSatisfiable(rangeString, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequested_PreconditionFailed(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_NotModified()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequested_NotModified(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent(action);
-        }
-
-        [Theory]
-        [InlineData(0, 3, 4)]
-        [InlineData(8, 13, 6)]
-        [InlineData(null, 3, 3)]
-        [InlineData(8, null, 26)]
-        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, long contentLength)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(start, end, contentLength, action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_WorksWithAbsolutePaths()
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_WorksWithAbsolutePaths(action);
-        }
-
-        [Theory]
-        [InlineData("FilePathResultTestFile.txt")]
-        [InlineData("./FilePathResultTestFile.txt")]
-        [InlineData(".\\FilePathResultTestFile.txt")]
-        [InlineData("~/FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
-        [InlineData("~/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("~/SubFolder\\SubFolderTestFile.txt")]
-        public async Task ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths(path, action);
-        }
-
-        [Theory]
-        [InlineData("/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("/SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("\\SubFolder/SubFolderTestFile.txt")]
-        [InlineData("./SubFolder/SubFolderTestFile.txt")]
-        [InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("./SubFolder\\SubFolderTestFile.txt")]
-        [InlineData(".\\SubFolder/SubFolderTestFile.txt")]
-        public void ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            BasePhysicalFileResultTest
-                .ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(path, action);
-        }
-
-        [Theory]
-        [InlineData("/FilePathResultTestFile.txt")]
-        [InlineData("\\FilePathResultTestFile.txt")]
-        public void ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            BasePhysicalFileResultTest
-                .ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(path, action);
-        }
-    }
-}
diff --git a/src/Mvc/Mvc.Core/test/PhysicalFileResult.cs b/src/Mvc/Mvc.Core/test/PhysicalFileResult.cs
deleted file mode 100644
index 5cadbb3db739..000000000000
--- a/src/Mvc/Mvc.Core/test/PhysicalFileResult.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc
-{
-    public class PhysicalFileResultTest
-    {
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 5, "ts�", 5)]
-        [InlineData(8, null, "ResultTestFile contents�", 26)]
-        public async Task WriteFileAsync_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_WritesRangeRequested(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 35-36")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequestedNotSatisfiable(rangeString, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
-        {var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequested_PreconditionFailed(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_NotModified()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.WriteFileAsync_RangeRequested_NotModified(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent(action);
-        }
-
-        [Theory]
-        [InlineData(0, 3, 4)]
-        [InlineData(8, 13, 6)]
-        [InlineData(null, 3, 3)]
-        [InlineData(8, null, 26)]
-        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, long contentLength)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(start, end, contentLength, action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_WorksWithAbsolutePaths()
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteResultAsync_WorksWithAbsolutePaths(action);
-        }
-
-        [Theory]
-        [InlineData("FilePathResultTestFile.txt")]
-        [InlineData("./FilePathResultTestFile.txt")]
-        [InlineData(".\\FilePathResultTestFile.txt")]
-        [InlineData("~/FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
-        [InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
-        [InlineData("~/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("~/SubFolder\\SubFolderTestFile.txt")]
-        public async Task ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BasePhysicalFileResultTest.ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths(path, action);
-        }
-
-        [Theory]
-        [InlineData("/SubFolder/SubFolderTestFile.txt")]
-        [InlineData("\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("/SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("\\SubFolder/SubFolderTestFile.txt")]
-        [InlineData("./SubFolder/SubFolderTestFile.txt")]
-        [InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
-        [InlineData("./SubFolder\\SubFolderTestFile.txt")]
-        [InlineData(".\\SubFolder/SubFolderTestFile.txt")]
-        public void ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            BasePhysicalFileResultTest
-                .ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(path, action);
-        }
-
-        [Theory]
-        [InlineData("/FilePathResultTestFile.txt")]
-        [InlineData("\\FilePathResultTestFile.txt")]
-        public void ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(string path)
-        {
-            var action = new Func<PhysicalFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            BasePhysicalFileResultTest
-                .ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(path, action);
-        }
-    }
-}
diff --git a/src/Mvc/Mvc.Core/test/PhysicalFileResultTest.cs b/src/Mvc/Mvc.Core/test/PhysicalFileResultTest.cs
new file mode 100644
index 000000000000..906ecdd52a7a
--- /dev/null
+++ b/src/Mvc/Mvc.Core/test/PhysicalFileResultTest.cs
@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+    public class PhysicalFileResultTest : PhysicalFileResultTestBase
+    {
+        protected override Task ExecuteAsync(
+            HttpContext httpContext,
+            string path,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false)
+        {
+            var fileResult = new PhysicalFileResult(path, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
+
+            httpContext.RequestServices = CreateServices();
+            var actionContext = new ActionContext(httpContext, new(), new());
+
+            return fileResult.ExecuteResultAsync(actionContext);
+        }
+
+        private class TestPhysicalFileResultExecutor : PhysicalFileResultExecutor
+        {
+            public TestPhysicalFileResultExecutor(ILoggerFactory loggerFactory)
+                : base(loggerFactory)
+            {
+            }
+
+            protected override FileMetadata GetFileInfo(string path)
+            {
+                var lastModified = DateTimeOffset.MinValue.AddDays(1);
+                return new FileMetadata
+                {
+                    Exists = true,
+                    Length = 34,
+                    LastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0))
+                };
+            }
+        }
+
+        private static IServiceProvider CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, TestPhysicalFileResultExecutor>();
+            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Mvc/Mvc.Core/test/RedirectResultTest.cs b/src/Mvc/Mvc.Core/test/RedirectResultTest.cs
index 68e310a82aac..8a170e892cba 100644
--- a/src/Mvc/Mvc.Core/test/RedirectResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/RedirectResultTest.cs
@@ -4,49 +4,79 @@
 using System;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Mvc.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Mvc
 {
-    public class RedirectResultTest
+    public class RedirectResultTest : RedirectResultTestBase
     {
-        [Theory]
-        [InlineData("", "/Home/About", "/Home/About")]
-        [InlineData("/myapproot", "/test", "/test")]
-        public async Task Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde(
-            string appRoot,
-            string contentPath,
-            string expectedPath)
+        protected override Task ExecuteAsync(HttpContext httpContext, string contentPath)
         {
-            var action
-                = new Func<RedirectResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseRedirectResultTest.Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde(
-                appRoot,
-                contentPath,
-                expectedPath,
-                action);
+            httpContext.RequestServices = GetServiceProvider();
+            var actionContext = new ActionContext(httpContext, new(), new());
+
+            var redirectResult = new RedirectResult(contentPath);
+            return redirectResult.ExecuteResultAsync(actionContext);
+        }
+
+        private static IServiceProvider GetServiceProvider()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IActionResultExecutor<RedirectResult>, RedirectResultExecutor>();
+            serviceCollection.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
+            serviceCollection.AddTransient<ILoggerFactory, NullLoggerFactory>();
+            return serviceCollection.BuildServiceProvider();
+        }
+
+        [Fact]
+        public void RedirectResult_Constructor_WithParameterUrl_SetsResultUrlAndNotPermanentOrPreserveMethod()
+        {
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new RedirectResult(url);
+
+            // Assert
+            Assert.False(result.PreserveMethod);
+            Assert.False(result.Permanent);
+            Assert.Same(url, result.Url);
+        }
+
+        [Fact]
+        public void RedirectResult_Constructor_WithParameterUrlAndPermanent_SetsResultUrlAndPermanentNotPreserveMethod()
+        {
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new RedirectResult(url, permanent: true);
+
+            // Assert
+            Assert.False(result.PreserveMethod);
+            Assert.True(result.Permanent);
+            Assert.Same(url, result.Url);
         }
 
-        [Theory]
-        [InlineData(null, "~/Home/About", "/Home/About")]
-        [InlineData("/", "~/Home/About", "/Home/About")]
-        [InlineData("/", "~/", "/")]
-        [InlineData("", "~/Home/About", "/Home/About")]
-        [InlineData("/myapproot", "~/", "/myapproot/")]
-        public async Task Execute_ReturnsAppRelativePath_WhenItStartsWithTilde(
-            string appRoot,
-            string contentPath,
-            string expectedPath)
+        [Fact]
+        public void RedirectResult_Constructor_WithParameterUrlPermanentAndPreservesMethod_SetsResultUrlPermanentAndPreservesMethod()
         {
-            var action
-                = new Func<RedirectResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseRedirectResultTest.Execute_ReturnsAppRelativePath_WhenItStartsWithTilde(
-                appRoot,
-                contentPath,
-                expectedPath,
-                action);
+            // Arrange
+            var url = "/test/url";
+
+            // Act
+            var result = new RedirectResult(url, permanent: true, preserveMethod: true);
+
+            // Assert
+            Assert.True(result.PreserveMethod);
+            Assert.True(result.Permanent);
+            Assert.Same(url, result.Url);
         }
     }
 }
diff --git a/src/Mvc/Mvc.Core/test/Routing/UrlHelperExtensionsTest.cs b/src/Mvc/Mvc.Core/test/Routing/UrlHelperExtensionsTest.cs
index 02ebfc549b52..0ea5e72b1027 100644
--- a/src/Mvc/Mvc.Core/test/Routing/UrlHelperExtensionsTest.cs
+++ b/src/Mvc/Mvc.Core/test/Routing/UrlHelperExtensionsTest.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
@@ -6,13 +6,12 @@
 using System.Linq;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc.Abstractions;
-using Microsoft.AspNetCore.Mvc.Routing;
 using Microsoft.AspNetCore.Routing;
 using Microsoft.AspNetCore.Testing;
 using Moq;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing
+namespace Microsoft.AspNetCore.Mvc.Routing
 {
     public class UrlHelperExtensionsTest
     {
diff --git a/src/Mvc/Mvc.Core/test/SignInResultTest.cs b/src/Mvc/Mvc.Core/test/SignInResultTest.cs
index b4a75abd9005..15f895bd5b5a 100644
--- a/src/Mvc/Mvc.Core/test/SignInResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/SignInResultTest.cs
@@ -100,70 +100,6 @@ public async Task ExecuteResultAsync_InvokesSignInAsyncOnConfiguredScheme()
             auth.Verify();
         }
 
-        [Fact]
-        public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManager()
-        {
-            // Arrange
-            var principal = new ClaimsPrincipal();
-            var httpContext = new Mock<HttpContext>();
-            var auth = new Mock<IAuthenticationService>();
-            auth
-                .Setup(c => c.SignInAsync(httpContext.Object, "", principal, null))
-                .Returns(Task.CompletedTask)
-                .Verifiable();
-            httpContext.Setup(c => c.RequestServices).Returns(CreateServices(auth.Object));
-            var result = new SignInResult("", principal, null);
-
-            // Act
-            await ((IResult)result).ExecuteAsync(httpContext.Object);
-
-            // Assert
-            auth.Verify();
-        }
-
-        [Fact]
-        public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManagerWithDefaultScheme()
-        {
-            // Arrange
-            var principal = new ClaimsPrincipal();
-            var httpContext = new Mock<HttpContext>();
-            var auth = new Mock<IAuthenticationService>();
-            auth
-                .Setup(c => c.SignInAsync(httpContext.Object, null, principal, null))
-                .Returns(Task.CompletedTask)
-                .Verifiable();
-            httpContext.Setup(c => c.RequestServices).Returns(CreateServices(auth.Object));
-            var result = new SignInResult(principal);
-
-            // Act
-            await ((IResult)result).ExecuteAsync(httpContext.Object);
-
-            // Assert
-            auth.Verify();
-        }
-
-        [Fact]
-        public async Task ExecuteAsync_InvokesSignInAsyncOnConfiguredScheme()
-        {
-            // Arrange
-            var principal = new ClaimsPrincipal();
-            var authProperties = new AuthenticationProperties();
-            var httpContext = new Mock<HttpContext>();
-            var auth = new Mock<IAuthenticationService>();
-            auth
-                .Setup(c => c.SignInAsync(httpContext.Object, "Scheme1", principal, authProperties))
-                .Returns(Task.CompletedTask)
-                .Verifiable();
-            httpContext.Setup(c => c.RequestServices).Returns(CreateServices(auth.Object));
-            var result = new SignInResult("Scheme1", principal, authProperties);
-
-            // Act
-            await ((IResult)result).ExecuteAsync(httpContext.Object);
-
-            // Assert
-            auth.Verify();
-        }
-
         private static IServiceProvider CreateServices(IAuthenticationService auth)
         {
             return new ServiceCollection()
diff --git a/src/Mvc/Mvc.Core/test/VirtualFileActionResultTest .cs b/src/Mvc/Mvc.Core/test/VirtualFileActionResultTest .cs
deleted file mode 100644
index ebc4f89f903e..000000000000
--- a/src/Mvc/Mvc.Core/test/VirtualFileActionResultTest .cs	
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Mvc
-{
-    public class VirtualFileActionResultTest
-    {
-        [Fact]
-        public void Constructor_SetsFileName()
-        {
-            // Arrange
-            var path = Path.GetFullPath("helllo.txt");
-
-            // Act
-            var result = new VirtualFileResult(path, "text/plain");
-
-            // Assert
-            Assert.Equal(path, result.FileName);
-        }
-
-        [Fact]
-        public void Constructor_SetsContentTypeAndParameters()
-        {
-            // Arrange
-            var path = Path.GetFullPath("helllo.txt");
-            var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var expectedMediaType = contentType;
-
-            // Act
-            var result = new VirtualFileResult(path, contentType);
-
-            // Assert
-            Assert.Equal(path, result.FileName);
-            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
-        }
-
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 4, "ts¡", 4)]
-        [InlineData(8, null, "ResultTestFile contents¡", 25)]
-        public async Task WriteFileAsync_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_WritesRangeRequested(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest
-                .WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-            await BaseVirtualFileResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 35-36")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequestedNotSatisfiable(rangeString, action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequested_PreconditionFailed(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_NotModified()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequested_NotModified(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent(action);
-        }
-
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 3, "ts¡", 3)]
-        [InlineData(8, null, "ResultTestFile contents¡", 25)]
-        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_ReturnsFileContentsForRelativePaths(action);
-        }
-
-        [Theory]
-        [InlineData("FilePathResultTestFile.txt")]
-        [InlineData("TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("TestFiles/../FilePathResultTestFile.txt")]
-        [InlineData("TestFiles\\FilePathResultTestFile.txt")]
-        [InlineData("TestFiles\\..\\FilePathResultTestFile.txt")]
-        [InlineData(@"\\..//?><|""&@#\c:\..\? /..txt")]
-        public async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(string path)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(path, action);
-        }
-
-        [Theory]
-        [InlineData("~/FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles/../FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles\\..\\FilePathResultTestFile.txt")]
-        [InlineData(@"~~~~\\..//?>~<|""&@#\c:\..\? /..txt~~~")]
-        public async Task ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider(string path)
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider(path, action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_WorksWithNonDiskBasedFiles(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile()
-        {
-            var action = new Func<VirtualFileResult, ActionContext, Task>(async (result, context) => await result.ExecuteResultAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile(action);
-        }
-    }
-}
diff --git a/src/Mvc/Mvc.Core/test/VirtualFileResultTest.cs b/src/Mvc/Mvc.Core/test/VirtualFileResultTest.cs
index 04a1f14037d4..e0a3d46c502f 100644
--- a/src/Mvc/Mvc.Core/test/VirtualFileResultTest.cs
+++ b/src/Mvc/Mvc.Core/test/VirtualFileResultTest.cs
@@ -2,186 +2,104 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
+using System.IO;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+using Moq;
 using Xunit;
 
 namespace Microsoft.AspNetCore.Mvc
 {
-    public class VirtualFileResultTest
+    public class VirtualFileResultTest : VirtualFileResultTestBase
     {
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 4, "ts¡", 4)]
-        [InlineData(8, null, "ResultTestFile contents¡", 25)]
-        public async Task WriteFileAsync_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_WritesRangeRequested(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange(action);
-        }
-
         [Fact]
-        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
+        public void Constructor_SetsFileName()
         {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored(action);
-        }
-
-        [Fact]
-        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored(action);
-        }
-
-        [Theory]
-        [InlineData("0-5")]
-        [InlineData("bytes = ")]
-        [InlineData("bytes = 1-4, 5-11")]
-        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(rangeString, action);
-        }
-
-        [Theory]
-        [InlineData("bytes = 35-36")]
-        [InlineData("bytes = -0")]
-        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequestedNotSatisfiable(rangeString, action);
-        }
+            // Arrange
+            var path = Path.GetFullPath("helllo.txt");
 
-        [Fact]
-        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Act
+            var result = new VirtualFileResult(path, "text/plain");
 
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequested_PreconditionFailed(action);
+            // Assert
+            Assert.Equal(path, result.FileName);
         }
 
         [Fact]
-        public async Task WriteFileAsync_RangeRequested_NotModified()
+        public void Constructor_SetsContentTypeAndParameters()
         {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.WriteFileAsync_RangeRequested_NotModified(action);
-        }
+            // Arrange
+            var path = Path.GetFullPath("helllo.txt");
+            var contentType = "text/plain; charset=us-ascii; p1=p1-value";
+            var expectedMediaType = contentType;
 
-        [Fact]
-        public async Task ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Act
+            var result = new VirtualFileResult(path, contentType);
 
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent(action);
+            // Assert
+            Assert.Equal(path, result.FileName);
+            MediaTypeAssert.Equal(expectedMediaType, result.ContentType);
         }
 
         [Fact]
-        public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
+        public void GetFileProvider_ReturnsFileProviderFromWebHostEnvironment()
         {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Arrange
+            var webHostFileProvider = Mock.Of<IFileProvider>();
+            var webHostEnvironment = Mock.Of<IWebHostEnvironment>(e => e.WebRootFileProvider == webHostFileProvider);
 
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent(action);
-        }
-
-        [Theory]
-        [InlineData(0, 3, "File", 4)]
-        [InlineData(8, 13, "Result", 6)]
-        [InlineData(null, 3, "ts¡", 3)]
-        [InlineData(8, null, "ResultTestFile contents¡", 25)]
-        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, string expectedString, long contentLength)
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(
-                start,
-                end,
-                expectedString,
-                contentLength,
-                action);
-        }
+            var result = new VirtualFileResult("some-path", "text/plain");
 
-        [Fact]
-        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Act
+            var fileProvider = VirtualFileResultExecutor.GetFileProvider(result, webHostEnvironment);
 
-            await BaseVirtualFileResultTest.ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding(action);
+            // Assert
+            Assert.Same(webHostFileProvider, fileProvider);
         }
 
         [Fact]
-        public async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths()
+        public void GetFileProvider_ReturnsFileProviderFromResult()
         {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Arrange
+            var webHostFileProvider = Mock.Of<IFileProvider>();
+            var fileProvider = Mock.Of<IFileProvider>();
+            var webHostEnvironment = Mock.Of<IWebHostEnvironment>(e => e.WebRootFileProvider == webHostFileProvider);
 
-            await BaseVirtualFileResultTest.ExecuteResultAsync_ReturnsFileContentsForRelativePaths(action);
-        }
+            var result = new VirtualFileResult("some-path", "text/plain") { FileProvider = fileProvider };
 
-        [Theory]
-        [InlineData("FilePathResultTestFile.txt")]
-        [InlineData("TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("TestFiles/../FilePathResultTestFile.txt")]
-        [InlineData("TestFiles\\FilePathResultTestFile.txt")]
-        [InlineData("TestFiles\\..\\FilePathResultTestFile.txt")]
-        [InlineData(@"\\..//?><|""&@#\c:\..\? /..txt")]
-        public async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(string path)
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            // Act
+            var actual = VirtualFileResultExecutor.GetFileProvider(result, webHostEnvironment);
 
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(path, action);
+            // Assert
+            Assert.Same(fileProvider, actual);
         }
 
-        [Theory]
-        [InlineData("~/FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles/FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles/../FilePathResultTestFile.txt")]
-        [InlineData("~/TestFiles\\..\\FilePathResultTestFile.txt")]
-        [InlineData(@"~~~~\\..//?>~<|""&@#\c:\..\? /..txt~~~")]
-        public async Task ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider(string path)
+        protected override Task ExecuteAsync(HttpContext httpContext, string path, string contentType, DateTimeOffset? lastModified = null, EntityTagHeaderValue entityTag = null, bool enableRangeProcessing = false)
         {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest
-                .ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider(path, action);
-        }
+            var webHostEnvironment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
+            httpContext.RequestServices = new ServiceCollection()
+                .AddSingleton(webHostEnvironment)
+                .AddTransient<IActionResultExecutor<VirtualFileResult>, VirtualFileResultExecutor>()
+                .AddTransient<ILoggerFactory, NullLoggerFactory>()
+                .BuildServiceProvider();
 
-        [Fact]
-        public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
-
-            await BaseVirtualFileResultTest.ExecuteResultAsync_WorksWithNonDiskBasedFiles(action);
-        }
-
-        [Fact]
-        public async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile()
-        {
-            var action = new Func<VirtualFileResult, HttpContext, Task>(async (result, context) => await ((IResult)result).ExecuteAsync(context));
+            var actionContext = new ActionContext(httpContext, new(), new());
+            var result = new VirtualFileResult(path, contentType)
+            {
+                LastModified = lastModified,
+                EntityTag = entityTag,
+                EnableRangeProcessing = enableRangeProcessing,
+            };
 
-            await BaseVirtualFileResultTest.ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile(action);
+            return result.ExecuteResultAsync(actionContext);
         }
     }
 }
diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj b/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
index 57e3effb81a7..73355eca919c 100644
--- a/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
+++ b/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
@@ -22,7 +22,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <Compile Include="..\..\Mvc.Core\src\Formatters\ResponseContentTypeHelper.cs" />
+    <Compile Include="$(SharedSourceRoot)ResponseContentTypeHelper.cs" LinkBase="Shared" />
     <Compile Include="..\..\Mvc.Core\src\Infrastructure\AsyncEnumerableReader.cs" />
   </ItemGroup>
 </Project>
diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonResultExecutor.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonResultExecutor.cs
index 2091b8494a9d..9e29fda66fe2 100644
--- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonResultExecutor.cs
+++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonResultExecutor.cs
@@ -5,6 +5,7 @@
 using System.Buffers;
 using System.Text;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.AspNetCore.WebUtilities;
@@ -100,7 +101,8 @@ public async Task ExecuteAsync(ActionContext context, JsonResult result)
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 result.ContentType,
                 response.ContentType,
-                DefaultContentType,
+                (DefaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs
index d31b1d61830d..2a6595cc9688 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs
@@ -4,9 +4,11 @@
 #nullable enable
 
 using System;
+using System.Text;
 using System.Text.Encodings.Web;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Html;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.AspNetCore.Mvc.ModelBinding;
@@ -112,7 +114,8 @@ public virtual async Task ExecuteAsync(ActionContext context, ViewComponentResul
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 result.ContentType,
                 response.ContentType,
-                ViewExecutor.DefaultContentType,
+                (ViewExecutor.DefaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.ViewFeatures/src/ViewExecutor.cs b/src/Mvc/Mvc.ViewFeatures/src/ViewExecutor.cs
index 2c9e7fd860ee..2f5bba01ae8b 100644
--- a/src/Mvc/Mvc.ViewFeatures/src/ViewExecutor.cs
+++ b/src/Mvc/Mvc.ViewFeatures/src/ViewExecutor.cs
@@ -6,7 +6,9 @@
 using System;
 using System.Diagnostics;
 using System.IO;
+using System.Text;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.AspNetCore.Mvc.Infrastructure;
 using Microsoft.AspNetCore.Mvc.ModelBinding;
@@ -222,7 +224,8 @@ protected async Task ExecuteAsync(
             ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
                 contentType,
                 response.ContentType,
-                DefaultContentType,
+                (DefaultContentType, Encoding.UTF8),
+                MediaType.GetEncoding,
                 out var resolvedContentType,
                 out var resolvedContentTypeEncoding);
 
diff --git a/src/Mvc/Mvc.Core/src/Formatters/ResponseContentTypeHelper.cs b/src/Shared/ResponseContentTypeHelper.cs
similarity index 68%
rename from src/Mvc/Mvc.Core/src/Formatters/ResponseContentTypeHelper.cs
rename to src/Shared/ResponseContentTypeHelper.cs
index 3e941e9199ca..19b33346fd11 100644
--- a/src/Mvc/Mvc.Core/src/Formatters/ResponseContentTypeHelper.cs
+++ b/src/Shared/ResponseContentTypeHelper.cs
@@ -1,11 +1,12 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System.Diagnostics;
+using System;
 using System.Text;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Net.Http.Headers;
 
-namespace Microsoft.AspNetCore.Mvc.Formatters
+namespace Microsoft.AspNetCore.Internal
 {
     internal static class ResponseContentTypeHelper
     {
@@ -22,29 +23,21 @@ internal static class ResponseContentTypeHelper
         /// encoding is used to write the response and the ContentType header is set to be "text/plain" without any
         /// "charset" information.
         /// </remarks>
-        /// <param name="actionResultContentType">ContentType set on the action result</param>
-        /// <param name="httpResponseContentType"><see cref="HttpResponse.ContentType"/> property set
-        /// on <see cref="HttpResponse"/></param>
-        /// <param name="defaultContentType">The default content type of the action result.</param>
-        /// <param name="resolvedContentType">The content type to be used for the response content type header</param>
-        /// <param name="resolvedContentTypeEncoding">Encoding to be used for writing the response</param>
         public static void ResolveContentTypeAndEncoding(
             string? actionResultContentType,
-            string httpResponseContentType,
-            string defaultContentType,
+            string? httpResponseContentType,
+            (string defaultContentType, Encoding defaultEncoding) @default,
+            Func<string, Encoding?> getEncoding,
             out string resolvedContentType,
             out Encoding resolvedContentTypeEncoding)
         {
-            Debug.Assert(defaultContentType != null);
-
-            var defaultContentTypeEncoding = MediaType.GetEncoding(defaultContentType);
-            Debug.Assert(defaultContentTypeEncoding != null);
+            var (defaultContentType, defaultContentTypeEncoding) = @default;
 
             // 1. User sets the ContentType property on the action result
             if (actionResultContentType != null)
             {
                 resolvedContentType = actionResultContentType;
-                var actionResultEncoding = MediaType.GetEncoding(actionResultContentType);
+                var actionResultEncoding = getEncoding(actionResultContentType);
                 resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding;
                 return;
             }
@@ -52,7 +45,7 @@ public static void ResolveContentTypeAndEncoding(
             // 2. User sets the ContentType property on the http response directly
             if (!string.IsNullOrEmpty(httpResponseContentType))
             {
-                var mediaTypeEncoding = MediaType.GetEncoding(httpResponseContentType);
+                var mediaTypeEncoding = getEncoding(httpResponseContentType);
                 if (mediaTypeEncoding != null)
                 {
                     resolvedContentType = httpResponseContentType;
@@ -71,5 +64,15 @@ public static void ResolveContentTypeAndEncoding(
             resolvedContentType = defaultContentType;
             resolvedContentTypeEncoding = defaultContentTypeEncoding;
         }
+
+        public static Encoding? GetEncoding(string mediaType)
+        {
+            if (MediaTypeHeaderValue.TryParse(mediaType, out var parsed))
+            {
+                return parsed.Encoding;
+            }
+
+            return default;
+        }
     }
 }
diff --git a/src/Shared/ResultsHelpers/FileResultHelper.cs b/src/Shared/ResultsHelpers/FileResultHelper.cs
new file mode 100644
index 000000000000..fb44351d4359
--- /dev/null
+++ b/src/Shared/ResultsHelpers/FileResultHelper.cs
@@ -0,0 +1,403 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Internal
+{
+    internal static partial class FileResultHelper
+    {
+        private const string AcceptRangeHeaderValue = "bytes";
+
+        internal enum PreconditionState
+        {
+            Unspecified,
+            NotModified,
+            ShouldProcess,
+            PreconditionFailed
+        }
+
+        internal static async Task WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue? range, long rangeLength)
+        {
+            const int BufferSize = 64 * 1024;
+            var outputStream = context.Response.Body;
+            using (fileStream)
+            {
+                try
+                {
+                    if (range == null)
+                    {
+                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count: null, bufferSize: 64 * 1024, cancel: context.RequestAborted);
+                    }
+                    else
+                    {
+                        fileStream.Seek(range.From!.Value, SeekOrigin.Begin);
+                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, rangeLength, BufferSize, context.RequestAborted);
+                    }
+                }
+                catch (OperationCanceledException)
+                {
+                    // Don't throw this exception, it's most likely caused by the client disconnecting.
+                    // However, if it was cancelled for any other reason we need to prevent empty responses.
+                    context.Abort();
+                }
+            }
+        }
+
+        internal static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetHeadersAndLog(
+            HttpContext httpContext,
+            in FileResultInfo result,
+            long? fileLength,
+            bool enableRangeProcessing,
+            DateTimeOffset? lastModified,
+            EntityTagHeaderValue? etag,
+            ILogger logger)
+        {
+            var request = httpContext.Request;
+            var httpRequestHeaders = request.GetTypedHeaders();
+
+            // Since the 'Last-Modified' and other similar http date headers are rounded down to whole seconds,
+            // round down current file's last modified to whole seconds for correct comparison.
+            if (lastModified.HasValue)
+            {
+                lastModified = RoundDownToWholeSeconds(lastModified.Value);
+            }
+
+            var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag, logger);
+
+            var response = httpContext.Response;
+            SetLastModifiedAndEtagHeaders(response, lastModified, etag);
+
+            // Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
+            if (preconditionState == PreconditionState.NotModified)
+            {
+                response.StatusCode = StatusCodes.Status304NotModified;
+                return (range: null, rangeLength: 0, serveBody: false);
+            }
+            else if (preconditionState == PreconditionState.PreconditionFailed)
+            {
+                response.StatusCode = StatusCodes.Status412PreconditionFailed;
+                return (range: null, rangeLength: 0, serveBody: false);
+            }
+
+
+            response.ContentType = result.ContentType;
+            SetContentDispositionHeader(httpContext, in result);
+
+            if (fileLength.HasValue)
+            {
+                // Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to
+                // the length of the entire file.
+                // If the request is a valid range request, this header is overwritten with the length of the range as part of the
+                // range processing (see method SetContentLength).
+
+                response.ContentLength = fileLength.Value;
+
+                // Handle range request
+                if (enableRangeProcessing)
+                {
+                    SetAcceptRangeHeader(response);
+
+                    // If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid,
+                    // range should be processed and Range headers should be set
+                    if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
+                        && (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess)
+                        && (IfRangeValid(httpRequestHeaders, lastModified, etag, logger)))
+                    {
+                        return SetRangeHeaders(httpContext, httpRequestHeaders, fileLength.Value, logger);
+                    }
+                }
+                else
+                {
+                    Log.NotEnabledForRangeProcessing(logger);
+                }
+            }
+
+            return (range: null, rangeLength: 0, serveBody: !HttpMethods.IsHead(request.Method));
+        }
+
+        internal static bool IfRangeValid(
+            RequestHeaders httpRequestHeaders,
+            DateTimeOffset? lastModified,
+            EntityTagHeaderValue? etag,
+            ILogger logger)
+        {
+            // 14.27 If-Range
+            var ifRange = httpRequestHeaders.IfRange;
+            if (ifRange != null)
+            {
+                // If the validator given in the If-Range header field matches the
+                // current validator for the selected representation of the target
+                // resource, then the server SHOULD process the Range header field as
+                // requested.  If the validator does not match, the server MUST ignore
+                // the Range header field.
+                if (ifRange.LastModified.HasValue)
+                {
+                    if (lastModified.HasValue && lastModified > ifRange.LastModified)
+                    {
+                        Log.IfRangeLastModifiedPreconditionFailed(logger, lastModified, ifRange.LastModified);
+                        return false;
+                    }
+                }
+                else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true))
+                {
+                    Log.IfRangeETagPreconditionFailed(logger, etag, ifRange.EntityTag);
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        internal static PreconditionState GetPreconditionState(
+            RequestHeaders httpRequestHeaders,
+            DateTimeOffset? lastModified,
+            EntityTagHeaderValue? etag,
+            ILogger logger)
+        {
+            var ifMatchState = PreconditionState.Unspecified;
+            var ifNoneMatchState = PreconditionState.Unspecified;
+            var ifModifiedSinceState = PreconditionState.Unspecified;
+            var ifUnmodifiedSinceState = PreconditionState.Unspecified;
+
+            // 14.24 If-Match
+            var ifMatch = httpRequestHeaders.IfMatch;
+            if (etag != null)
+            {
+                ifMatchState = GetEtagMatchState(
+                    useStrongComparison: true,
+                    etagHeader: ifMatch,
+                    etag: etag,
+                    matchFoundState: PreconditionState.ShouldProcess,
+                    matchNotFoundState: PreconditionState.PreconditionFailed);
+
+                if (ifMatchState == PreconditionState.PreconditionFailed)
+                {
+                    Log.IfMatchPreconditionFailed(logger, etag);
+                }
+            }
+
+            // 14.26 If-None-Match
+            var ifNoneMatch = httpRequestHeaders.IfNoneMatch;
+            if (etag != null)
+            {
+                ifNoneMatchState = GetEtagMatchState(
+                    useStrongComparison: false,
+                    etagHeader: ifNoneMatch,
+                    etag: etag,
+                    matchFoundState: PreconditionState.NotModified,
+                    matchNotFoundState: PreconditionState.ShouldProcess);
+            }
+
+            var now = RoundDownToWholeSeconds(DateTimeOffset.UtcNow);
+
+            // 14.25 If-Modified-Since
+            var ifModifiedSince = httpRequestHeaders.IfModifiedSince;
+            if (lastModified.HasValue && ifModifiedSince.HasValue && ifModifiedSince <= now)
+            {
+                var modified = ifModifiedSince < lastModified;
+                ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
+            }
+
+            // 14.28 If-Unmodified-Since
+            var ifUnmodifiedSince = httpRequestHeaders.IfUnmodifiedSince;
+            if (lastModified.HasValue && ifUnmodifiedSince.HasValue && ifUnmodifiedSince <= now)
+            {
+                var unmodified = ifUnmodifiedSince >= lastModified;
+                ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
+
+                if (ifUnmodifiedSinceState == PreconditionState.PreconditionFailed)
+                {
+                    Log.IfUnmodifiedSincePreconditionFailed(logger, lastModified, ifUnmodifiedSince);
+                }
+            }
+
+            var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState);
+            return state;
+        }
+
+        private static PreconditionState GetEtagMatchState(
+            bool useStrongComparison,
+            IList<EntityTagHeaderValue> etagHeader,
+            EntityTagHeaderValue etag,
+            PreconditionState matchFoundState,
+            PreconditionState matchNotFoundState)
+        {
+            if (etagHeader?.Count > 0)
+            {
+                var state = matchNotFoundState;
+                foreach (var entityTag in etagHeader)
+                {
+                    if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison))
+                    {
+                        state = matchFoundState;
+                        break;
+                    }
+                }
+
+                return state;
+            }
+
+            return PreconditionState.Unspecified;
+        }
+
+        private static (RangeItemHeaderValue? range, long rangeLength, bool serveBody) SetRangeHeaders(
+            HttpContext httpContext,
+            RequestHeaders httpRequestHeaders,
+            long fileLength,
+            ILogger logger)
+        {
+            var response = httpContext.Response;
+            var httpResponseHeaders = response.GetTypedHeaders();
+            var serveBody = !HttpMethods.IsHead(httpContext.Request.Method);
+
+            // Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges
+            // and when the file length is zero.
+            var (isRangeRequest, range) = RangeHelper.ParseRange(
+                httpContext,
+                httpRequestHeaders,
+                fileLength,
+                logger);
+
+            if (!isRangeRequest)
+            {
+                return (range: null, rangeLength: 0, serveBody);
+            }
+
+            // Requested range is not satisfiable
+            if (range == null)
+            {
+                // 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
+                // SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
+                // the current length of the selected resource.  e.g. */length
+                response.StatusCode = StatusCodes.Status416RangeNotSatisfiable;
+                httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(fileLength);
+                response.ContentLength = 0;
+
+                return (range: null, rangeLength: 0, serveBody: false);
+            }
+
+            response.StatusCode = StatusCodes.Status206PartialContent;
+            httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(
+                range.From!.Value,
+                range.To!.Value,
+                fileLength);
+
+            // Overwrite the Content-Length header for valid range requests with the range length.
+            var rangeLength = SetContentLength(response, range);
+
+            return (range, rangeLength, serveBody);
+        }
+
+        private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range)
+        {
+            var start = range.From!.Value;
+            var end = range.To!.Value;
+            var length = end - start + 1;
+            response.ContentLength = length;
+            return length;
+        }
+
+        private static void SetContentDispositionHeader(HttpContext httpContext, in FileResultInfo result)
+        {
+            if (!string.IsNullOrEmpty(result.FileDownloadName))
+            {
+                // From RFC 2183, Sec. 2.3:
+                // The sender may want to suggest a filename to be used if the entity is
+                // detached and stored in a separate file. If the receiving MUA writes
+                // the entity to a file, the suggested filename should be used as a
+                // basis for the actual filename, where possible.
+                var contentDisposition = new ContentDispositionHeaderValue("attachment");
+                contentDisposition.SetHttpFileName(result.FileDownloadName);
+                httpContext.Response.Headers.ContentDisposition = contentDisposition.ToString();
+            }
+        }
+
+        private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset? lastModified, EntityTagHeaderValue? etag)
+        {
+            var httpResponseHeaders = response.GetTypedHeaders();
+            if (lastModified.HasValue)
+            {
+                httpResponseHeaders.LastModified = lastModified;
+            }
+            if (etag != null)
+            {
+                httpResponseHeaders.ETag = etag;
+            }
+        }
+
+        private static void SetAcceptRangeHeader(HttpResponse response)
+        {
+            response.Headers.AcceptRanges = AcceptRangeHeaderValue;
+        }
+
+        private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
+        {
+            var max = PreconditionState.Unspecified;
+            for (var i = 0; i < states.Length; i++)
+            {
+                if (states[i] > max)
+                {
+                    max = states[i];
+                }
+            }
+
+            return max;
+        }
+
+        private static DateTimeOffset RoundDownToWholeSeconds(DateTimeOffset dateTimeOffset)
+        {
+            var ticksToRemove = dateTimeOffset.Ticks % TimeSpan.TicksPerSecond;
+            return dateTimeOffset.Subtract(TimeSpan.FromTicks(ticksToRemove));
+        }
+
+        internal static partial class Log
+        {
+            [LoggerMessage(17, LogLevel.Debug, "Writing the requested range of bytes to the body.", EventName = "WritingRangeToBody")]
+            public static partial void WritingRangeToBody(ILogger logger);
+
+            [LoggerMessage(34, LogLevel.Debug,
+                "Current request's If-Match header check failed as the file's current etag '{CurrentETag}' does not match with any of the supplied etags.",
+                EventName = "IfMatchPreconditionFailed")]
+            public static partial void IfMatchPreconditionFailed(ILogger logger, EntityTagHeaderValue currentETag);
+
+            [LoggerMessage(35, LogLevel.Debug,
+                "Current request's If-Unmodified-Since header check failed as the file was modified (at '{lastModified}') after the If-Unmodified-Since date '{IfUnmodifiedSinceDate}'.",
+                EventName = "IfUnmodifiedSincePreconditionFailed")]
+            public static partial void IfUnmodifiedSincePreconditionFailed(
+                ILogger logger,
+                DateTimeOffset? lastModified,
+                DateTimeOffset? ifUnmodifiedSinceDate);
+
+            [LoggerMessage(36, LogLevel.Debug,
+                "Could not serve range as the file was modified (at {LastModified}) after the if-Range's last modified date '{IfRangeLastModified}'.",
+                EventName = "IfRangeLastModifiedPreconditionFailed")]
+            public static partial void IfRangeLastModifiedPreconditionFailed(
+                ILogger logger,
+                DateTimeOffset? lastModified,
+                DateTimeOffset? IfRangeLastModified);
+
+            [LoggerMessage(37, LogLevel.Debug,
+                "Could not serve range as the file's current etag '{CurrentETag}' does not match the If-Range etag '{IfRangeETag}'.",
+                EventName = "IfRangeETagPreconditionFailed")]
+            public static partial void IfRangeETagPreconditionFailed(
+                ILogger logger,
+                EntityTagHeaderValue currentETag,
+                EntityTagHeaderValue IfRangeETag);
+
+            [LoggerMessage(38, LogLevel.Debug,
+                "The file result has not been enabled for processing range requests. To enable it, set the EnableRangeProcessing property on the result to 'true'.",
+                EventName = "NotEnabledForRangeProcessing")]
+            public static partial void NotEnabledForRangeProcessing(ILogger logger);
+        }
+
+    }
+}
diff --git a/src/Shared/ResultsHelpers/FileResultInfo.cs b/src/Shared/ResultsHelpers/FileResultInfo.cs
new file mode 100644
index 000000000000..59bd4d05d72d
--- /dev/null
+++ b/src/Shared/ResultsHelpers/FileResultInfo.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Internal
+{
+    internal readonly struct FileResultInfo
+    {
+        public string ContentType { get; init; }
+
+        public string FileDownloadName { get; init; }
+
+        public DateTimeOffset? LastModified { get; init; }
+
+        public EntityTagHeaderValue? EntityTag { get; init; }
+
+        public bool EnableRangeProcessing { get; init; }
+    }
+}
diff --git a/src/Shared/ResultsHelpers/FileResultLogging.cs b/src/Shared/ResultsHelpers/FileResultLogging.cs
new file mode 100644
index 000000000000..6f1b1c0a6d3d
--- /dev/null
+++ b/src/Shared/ResultsHelpers/FileResultLogging.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Internal
+{
+    internal interface IFileResultLogger
+    {
+        void IfUnmodifiedSincePreconditionFailed(
+            DateTimeOffset? lastModified,
+            DateTimeOffset? ifUnmodifiedSinceDate);
+
+        void IfMatchPreconditionFailed(EntityTagHeaderValue etag);
+
+        void NotEnabledForRangeProcessing();
+    }
+}
diff --git a/src/Shared/ResultsHelpers/SharedUrlHelper.cs b/src/Shared/ResultsHelpers/SharedUrlHelper.cs
new file mode 100644
index 000000000000..9f6732535a01
--- /dev/null
+++ b/src/Shared/ResultsHelpers/SharedUrlHelper.cs
@@ -0,0 +1,93 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Internal
+{
+    internal static class SharedUrlHelper
+    {
+        [return: NotNullIfNotNull("contentPath")]
+        internal static string? Content(HttpContext httpContext, string? contentPath)
+        {
+            if (string.IsNullOrEmpty(contentPath))
+            {
+                return null;
+            }
+            else if (contentPath[0] == '~')
+            {
+                var segment = new PathString(contentPath.Substring(1));
+                var applicationPath = httpContext.Request.PathBase;
+
+                var path = applicationPath.Add(segment);
+                Debug.Assert(path.HasValue);
+                return path.Value;
+            }
+
+            return contentPath;
+        }
+
+        internal static bool IsLocalUrl([NotNullWhen(true)] string? url)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                return false;
+            }
+
+            // Allows "/" or "/foo" but not "//" or "/\".
+            if (url[0] == '/')
+            {
+                // url is exactly "/"
+                if (url.Length == 1)
+                {
+                    return true;
+                }
+
+                // url doesn't start with "//" or "/\"
+                if (url[1] != '/' && url[1] != '\\')
+                {
+                    return !HasControlCharacter(url.AsSpan(1));
+                }
+
+                return false;
+            }
+
+            // Allows "~/" or "~/foo" but not "~//" or "~/\".
+            if (url[0] == '~' && url.Length > 1 && url[1] == '/')
+            {
+                // url is exactly "~/"
+                if (url.Length == 2)
+                {
+                    return true;
+                }
+
+                // url doesn't start with "~//" or "~/\"
+                if (url[2] != '/' && url[2] != '\\')
+                {
+                    return !HasControlCharacter(url.AsSpan(2));
+                }
+
+                return false;
+            }
+
+            return false;
+
+            static bool HasControlCharacter(ReadOnlySpan<char> readOnlySpan)
+            {
+                // URLs may not contain ASCII control characters.
+                for (var i = 0; i < readOnlySpan.Length; i++)
+                {
+                    if (char.IsControl(readOnlySpan[i]))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        }
+    }
+}
diff --git a/src/Mvc/Mvc.Core/test/BaseFileContentResultTest.cs b/src/Shared/ResultsTests/FileContentResultTestBase.cs
similarity index 60%
rename from src/Mvc/Mvc.Core/test/BaseFileContentResultTest.cs
rename to src/Shared/ResultsTests/FileContentResultTestBase.cs
index cb397e816a81..dace01e7e073 100644
--- a/src/Mvc/Mvc.Core/test/BaseFileContentResultTest.cs
+++ b/src/Shared/ResultsTests/FileContentResultTestBase.cs
@@ -6,48 +6,51 @@
 using System.Text;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Abstractions;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Routing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 using Microsoft.Net.Http.Headers;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Internal
 {
-    public class BaseFileContentResultTest
+    public abstract class FileContentResultTestBase
     {
-        public static async Task WriteFileAsync_CopiesBuffer_ToOutputStream<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        protected abstract Task ExecuteAsync(
+            HttpContext httpContext,
+            byte[] buffer,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false);
+
+        [Fact]
+        public async Task WriteFileAsync_CopiesBuffer_ToOutputStream()
         {
             // Arrange
             var buffer = new byte[] { 1, 2, 3, 4, 5 };
-
             var httpContext = GetHttpContext();
 
             var outStream = new MemoryStream();
             httpContext.Response.Body = outStream;
 
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            var result = new FileContentResult(buffer, "text/plain");
-
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, buffer, "text/plain");
 
             // Assert
             Assert.Equal(buffer, outStream.ToArray());
         }
 
-        public static async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested<TContext>(
+        [Theory]
+        [InlineData(0, 4, "Hello", 5)]
+        [InlineData(6, 10, "World", 5)]
+        [InlineData(null, 5, "World", 5)]
+        [InlineData(6, null, "World", 5)]
+        public async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(
             long? start,
             long? end,
             string expectedString,
-            long contentLength,
-            Func<FileContentResult, TContext, Task> function)
+            long contentLength)
         {
             // Arrange
             var contentType = "text/plain";
@@ -55,13 +58,6 @@ public static async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRan
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.Range = new RangeHeaderValue(start, end);
@@ -71,16 +67,14 @@ public static async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRan
             };
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
             start = start ?? 11 - end;
             end = start + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -94,8 +88,8 @@ public static async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRan
             Assert.Equal(expectedString, body);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest()
         {
             // Arrange
             var contentType = "text/plain";
@@ -103,13 +97,6 @@ public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest<TC
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfMatch = new[]
@@ -120,39 +107,28 @@ public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRangeRequest<TC
             requestHeaders.IfRange = new RangeConditionHeaderValue(DateTimeOffset.MinValue);
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag: entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
             Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
             Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
 
-            if (result.EnableRangeProcessing)
-            {
-                Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
-                Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
-                var contentRange = new ContentRangeHeaderValue(0, 4, byteArray.Length);
-                Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
-                Assert.Equal(5, httpResponse.ContentLength);
-                Assert.Equal("Hello", body);
-            }
-            else
-            {
-                Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
-                Assert.Equal(11, httpResponse.ContentLength);
-                Assert.Equal("Hello World", body);
-            }
+            Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
+            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
+            var contentRange = new ContentRangeHeaderValue(0, 4, byteArray.Length);
+            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
+            Assert.Equal(5, httpResponse.ContentLength);
+            Assert.Equal("Hello", body);
         }
 
-        public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIgnored()
         {
             // Arrange
             var contentType = "text/plain";
@@ -160,12 +136,6 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIg
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfMatch = new[]
@@ -176,14 +146,12 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIg
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -193,8 +161,8 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestIg
             Assert.Equal("Hello World", body);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored()
         {
             // Arrange
             var contentType = "text/plain";
@@ -202,13 +170,6 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfMatch = new[]
@@ -219,14 +180,12 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"NotEtag\""));
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -236,9 +195,11 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestIgnored
             Assert.Equal("Hello World", body);
         }
 
-        public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored<TContext>(
-            string rangeString,
-            Func<FileContentResult, TContext, Task> function)
+        [Theory]
+        [InlineData("0-5")]
+        [InlineData("bytes = ")]
+        [InlineData("bytes = 1-4, 5-11")]
+        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
         {
             // Arrange
             var contentType = "text/plain";
@@ -246,25 +207,16 @@ public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeReques
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             httpContext.Request.Headers.Range = rangeString;
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -275,9 +227,10 @@ public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeReques
             Assert.Equal("Hello World", body);
         }
 
-        public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable<TContext>(
-            string rangeString,
-            Func<FileContentResult, TContext, Task> function)
+        [Theory]
+        [InlineData("bytes = 12-13")]
+        [InlineData("bytes = -0")]
+        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
         {
             // Arrange
             var contentType = "text/plain";
@@ -285,25 +238,16 @@ public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeReques
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             httpContext.Request.Headers.Range = rangeString;
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -317,8 +261,8 @@ public static async Task WriteFileAsync_PreconditionStateUnspecified_RangeReques
             Assert.Empty(body);
         }
 
-        public static async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored()
         {
             // Arrange
             var contentType = "text/plain";
@@ -326,30 +270,21 @@ public static async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfMatch = new[]
             {
-                new EntityTagHeaderValue("\"NotEtag\""),
-            };
+                    new EntityTagHeaderValue("\"NotEtag\""),
+                };
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -360,22 +295,15 @@ public static async Task WriteFileAsync_PreconditionFailed_RangeRequestedIgnored
             Assert.Empty(body);
         }
 
-        public static async Task WriteFileAsync_NotModified_RangeRequestedIgnored<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_NotModified_RangeRequestedIgnored()
         {
-            // Arrange       
+            // Arrange
             var contentType = "text/plain";
             var lastModified = new DateTimeOffset();
             var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var byteArray = Encoding.ASCII.GetBytes("Hello World");
 
-            var result = new FileContentResult(byteArray, contentType)
-            {
-                LastModified = lastModified,
-                EntityTag = entityTag,
-                EnableRangeProcessing = true,
-            };
-
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfNoneMatch = new[]
@@ -385,14 +313,12 @@ public static async Task WriteFileAsync_NotModified_RangeRequestedIgnored<TConte
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, byteArray, contentType, lastModified, entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -404,8 +330,8 @@ public static async Task WriteFileAsync_NotModified_RangeRequestedIgnored<TConte
             Assert.Empty(body);
         }
 
-        public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<TContext>(
-            Func<FileContentResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
         {
             // Arrange
             var expectedContentType = "text/foo; charset=us-ascii";
@@ -416,13 +342,8 @@ public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<T
             var outStream = new MemoryStream();
             httpContext.Response.Body = outStream;
 
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            var result = new FileContentResult(buffer, expectedContentType);
-
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, buffer, expectedContentType);
 
             // Assert
             Assert.Equal(buffer, outStream.ToArray());
@@ -432,7 +353,6 @@ public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<T
         private static IServiceCollection CreateServices()
         {
             var services = new ServiceCollection();
-            services.AddSingleton<IActionResultExecutor<FileContentResult>, FileContentResultExecutor>();
             services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
 
             return services;
diff --git a/src/Shared/ResultsTests/FileStreamResultTestBase.cs b/src/Shared/ResultsTests/FileStreamResultTestBase.cs
new file mode 100644
index 000000000000..f69c5d217346
--- /dev/null
+++ b/src/Shared/ResultsTests/FileStreamResultTestBase.cs
@@ -0,0 +1,463 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Internal
+{
+    public abstract class FileStreamResultTestBase
+    {
+        protected abstract Task ExecuteAsync(
+            HttpContext httpContext,
+            Stream stream,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false);
+
+        [Theory]
+        [InlineData(0, 4, "Hello", 5)]
+        [InlineData(6, 10, "World", 5)]
+        [InlineData(null, 5, "World", 5)]
+        [InlineData(6, null, "World", 5)]
+        public async Task WriteFileAsync_PreconditionStateShouldProcess_WritesRangeRequested(long? start, long? end, string expectedString, long contentLength)
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+            readStream.SetLength(11);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.Range = new RangeHeaderValue(start, end);
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            start = start ?? 11 - end;
+            end = start + contentLength - 1;
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            var contentRange = new ContentRangeHeaderValue(start.Value, end.Value, byteArray.Length);
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
+            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
+            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
+            Assert.Equal(contentLength, httpResponse.ContentLength);
+            Assert.Equal(expectedString, body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = DateTimeOffset.MinValue;
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+            readStream.SetLength(11);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            requestHeaders.Range = new RangeHeaderValue(0, 4);
+            requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            var contentRange = new ContentRangeHeaderValue(0, 4, byteArray.Length);
+            Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
+            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
+            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
+            Assert.Equal(5, httpResponse.ContentLength);
+            Assert.Equal("Hello", body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = DateTimeOffset.MinValue;
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+            readStream.SetLength(11);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            requestHeaders.Range = new RangeHeaderValue(0, 4);
+            requestHeaders.IfRange = new RangeConditionHeaderValue(DateTimeOffset.MinValue);
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            Assert.Equal("Hello World", body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = DateTimeOffset.MinValue.AddDays(1);
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+            readStream.SetLength(11);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            requestHeaders.Range = new RangeHeaderValue(0, 4);
+            requestHeaders.IfRange = new RangeConditionHeaderValue(DateTimeOffset.MinValue);
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            Assert.Equal("Hello World", body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Theory]
+        [InlineData("0-5")]
+        [InlineData("bytes = ")]
+        [InlineData("bytes = 1-4, 5-11")]
+        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
+        {
+            // Arrange            
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            httpContext.Request.Headers.Range = rangeString;
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Empty(httpResponse.Headers.ContentRange);
+            Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            Assert.Equal("Hello World", body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Theory]
+        [InlineData("bytes = 12-13")]
+        [InlineData("bytes = -0")]
+        public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
+        {
+            // Arrange            
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+
+            var httpContext = GetHttpContext();
+            httpContext.Request.Headers.Range = rangeString;
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            var contentRange = new ContentRangeHeaderValue(byteArray.Length);
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            Assert.Equal(StatusCodes.Status416RangeNotSatisfiable, httpResponse.StatusCode);
+            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
+            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
+            Assert.Equal(0, httpResponse.ContentLength);
+            Assert.Empty(body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"NotEtag\""),
+            };
+            httpContext.Request.Headers.Range = "bytes = 0-6";
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Equal(StatusCodes.Status412PreconditionFailed, httpResponse.StatusCode);
+            Assert.Null(httpResponse.ContentLength);
+            Assert.Empty(httpResponse.Headers.ContentRange);
+            Assert.NotEmpty(httpResponse.Headers.LastModified);
+            Assert.Empty(body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_NotModified_RangeRequestedIgnored()
+        {
+            // Arrange       
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("Hello World");
+            var readStream = new MemoryStream(byteArray);
+
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.IfNoneMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            httpContext.Request.Headers.Range = "bytes = 0-6";
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = await streamReader.ReadToEndAsync();
+            Assert.Equal(StatusCodes.Status304NotModified, httpResponse.StatusCode);
+            Assert.Null(httpResponse.ContentLength);
+            Assert.Empty(httpResponse.Headers.ContentRange);
+            Assert.False(httpResponse.Headers.ContainsKey(HeaderNames.ContentType));
+            Assert.NotEmpty(httpResponse.Headers.LastModified);
+            Assert.Empty(body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Theory]
+        [InlineData(0)]
+        [InlineData(null)]
+        public async Task WriteFileAsync_RangeRequested_FileLengthZeroOrNull(long? fileLength)
+        {
+            // Arrange
+            var contentType = "text/plain";
+            var lastModified = new DateTimeOffset();
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
+            var byteArray = Encoding.ASCII.GetBytes("");
+            var readStream = new MemoryStream(byteArray);
+            fileLength = fileLength ?? 0L;
+            readStream.SetLength(fileLength.Value);
+            
+            var httpContext = GetHttpContext();
+            var requestHeaders = httpContext.Request.GetTypedHeaders();
+            requestHeaders.Range = new RangeHeaderValue(0, 5);
+            requestHeaders.IfMatch = new[]
+            {
+                new EntityTagHeaderValue("\"Etag\""),
+            };
+            httpContext.Request.Method = HttpMethods.Get;
+            httpContext.Response.Body = new MemoryStream();
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, contentType, lastModified, entityTag, enableRangeProcessing: true);
+
+            // Assert
+            var httpResponse = httpContext.Response;
+            httpResponse.Body.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(httpResponse.Body);
+            var body = streamReader.ReadToEndAsync().Result;
+            Assert.Equal(lastModified.ToString("R"), httpResponse.Headers.LastModified);
+            Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
+            var contentRange = new ContentRangeHeaderValue(byteArray.Length);
+            Assert.Equal(StatusCodes.Status416RangeNotSatisfiable, httpResponse.StatusCode);
+            Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
+            Assert.Equal(contentRange.ToString(), httpResponse.Headers.ContentRange);
+            Assert.Equal(0, httpResponse.ContentLength);
+            Assert.Empty(body);
+            Assert.False(readStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task WriteFileAsync_CopiesProvidedStream_ToOutputStream()
+        {
+            // Arrange
+            // Generate an array of bytes with a predictable pattern
+            // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13
+            var originalBytes = Enumerable.Range(0, 0x1234)
+                .Select(b => (byte)(b % 20)).ToArray();
+
+            var originalStream = new MemoryStream(originalBytes);
+
+            var httpContext = GetHttpContext();
+            var outStream = new MemoryStream();
+            httpContext.Response.Body = outStream;
+
+            // Act
+            await ExecuteAsync(httpContext, originalStream, "text/plian");
+
+            // Assert
+            var outBytes = outStream.ToArray();
+            Assert.True(originalBytes.SequenceEqual(outBytes));
+            Assert.False(originalStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task SetsSuppliedContentTypeAndEncoding()
+        {
+            // Arrange
+            var expectedContentType = "text/foo; charset=us-ascii";
+            // Generate an array of bytes with a predictable pattern
+            // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13
+            var originalBytes = Enumerable.Range(0, 0x1234)
+                .Select(b => (byte)(b % 20)).ToArray();
+
+            var originalStream = new MemoryStream(originalBytes);
+
+            var httpContext = GetHttpContext();
+            var outStream = new MemoryStream();
+            httpContext.Response.Body = outStream;
+
+            // Act
+            await ExecuteAsync(httpContext, originalStream, expectedContentType);
+
+            // Assert
+            var outBytes = outStream.ToArray();
+            Assert.True(originalBytes.SequenceEqual(outBytes));
+            Assert.Equal(expectedContentType, httpContext.Response.ContentType);
+            Assert.False(originalStream.CanSeek);
+        }
+
+        [Fact]
+        public async Task HeadRequest_DoesNotWriteToBody_AndClosesReadStream()
+        {
+            // Arrange
+            var readStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello, World!"));
+
+            var httpContext = GetHttpContext();
+            httpContext.Request.Method = "HEAD";
+            var outStream = new MemoryStream();
+            httpContext.Response.Body = outStream;
+
+            // Act
+            await ExecuteAsync(httpContext, readStream, "text/plain");
+
+            // Assert
+            Assert.False(readStream.CanSeek);
+            Assert.Equal(200, httpContext.Response.StatusCode);
+            Assert.Equal(0, httpContext.Response.Body.Length);
+        }
+
+        private static IServiceCollection CreateServices()
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+            services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            return services;
+        }
+
+        private static HttpContext GetHttpContext()
+        {
+            var services = CreateServices();
+
+            var httpContext = new DefaultHttpContext();
+            httpContext.RequestServices = services.BuildServiceProvider();
+            return httpContext;
+        }
+    }
+}
diff --git a/src/Mvc/Mvc.Core/test/BasePhysicalFileResultTest.cs b/src/Shared/ResultsTests/PhysicalFileResultTestBase.cs
similarity index 58%
rename from src/Mvc/Mvc.Core/test/BasePhysicalFileResultTest.cs
rename to src/Shared/ResultsTests/PhysicalFileResultTestBase.cs
index c4d8960e879e..faaa01480cfa 100644
--- a/src/Mvc/Mvc.Core/test/BasePhysicalFileResultTest.cs
+++ b/src/Shared/ResultsTests/PhysicalFileResultTestBase.cs
@@ -2,16 +2,12 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.IO.Pipelines;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Mvc.Abstractions;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Routing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
@@ -19,21 +15,27 @@
 using Moq;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Internal
 {
-    public class BasePhysicalFileResultTest
+    public abstract class PhysicalFileResultTestBase
     {
-        public static async Task WriteFileAsync_WritesRangeRequested<TContext>(
-            long? start,
-            long? end,
-            string expectedString,
-            long contentLength,
-            Func<PhysicalFileResult, TContext, Task> function)
+        protected abstract Task ExecuteAsync(
+            HttpContext httpContext,
+            string path,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false);
+
+        [Theory]
+        [InlineData(0, 3, 4)]
+        [InlineData(8, 13, 6)]
+        [InlineData(null, 5, 5)]
+        [InlineData(8, null, 26)]
+        public async Task WriteFileAsync_WritesRangeRequested(long? start, long? end, long contentLength)
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
@@ -41,16 +43,14 @@ public static async Task WriteFileAsync_WritesRangeRequested<TContext>(
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             requestHeaders.Range = new RangeHeaderValue(start, end);
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
             var startResult = start ?? 34 - end;
             var endResult = startResult + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             var contentRange = new ContentRangeHeaderValue(startResult.Value, endResult.Value, 34);
             Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
             Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
@@ -62,14 +62,12 @@ public static async Task WriteFileAsync_WritesRangeRequested<TContext>(
             Assert.Equal((long?)contentLength, sendFile.Length);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
-            result.EnableRangeProcessing = true;
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
@@ -78,14 +76,12 @@ public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange<
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", entityTag: entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
             Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
             var contentRange = new ContentRangeHeaderValue(0, 3, 34);
@@ -98,13 +94,12 @@ public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange<
             Assert.Equal(4, sendFile.Length);
         }
 
-        public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
@@ -113,14 +108,12 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequested
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", entityTag: entityTag);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.NotEmpty(httpResponse.Headers.LastModified);
             Assert.Equal(Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")), sendFile.Name);
@@ -128,14 +121,12 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequested
             Assert.Null(sendFile.Length);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
@@ -144,14 +135,12 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnor
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"NotEtag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", entityTag: entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.NotEmpty(httpResponse.Headers.LastModified);
             Assert.Equal(Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")), sendFile.Name);
@@ -159,13 +148,14 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnor
             Assert.Null(sendFile.Length);
         }
 
-        public static async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored<TContext>(
-            string rangeString,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("0-5")]
+        [InlineData("bytes = ")]
+        [InlineData("bytes = 1-4, 5-11")]
+        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
@@ -173,14 +163,12 @@ public static async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             httpContext.Request.Headers.Range = rangeString;
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.Empty(httpResponse.Headers.ContentRange);
             Assert.NotEmpty(httpResponse.Headers.LastModified);
@@ -189,28 +177,25 @@ public static async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored
             Assert.Null(sendFile.Length);
         }
 
-        public static async Task WriteFileAsync_RangeRequestedNotSatisfiable<TContext>(
-            string rangeString,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("bytes = 35-36")]
+        [InlineData("bytes = -0")]
+        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             httpContext.Request.Headers.Range = rangeString;
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -223,27 +208,23 @@ public static async Task WriteFileAsync_RangeRequestedNotSatisfiable<TContext>(
             Assert.Empty(body);
         }
 
-        public static async Task WriteFileAsync_RangeRequested_PreconditionFailed<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue;
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -254,27 +235,23 @@ public static async Task WriteFileAsync_RangeRequested_PreconditionFailed<TConte
             Assert.Empty(body);
         }
 
-        public static async Task WriteFileAsync_RangeRequested_NotModified<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeRequested_NotModified()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
             var httpContext = GetHttpContext();
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -286,12 +263,11 @@ public static async Task WriteFileAsync_RangeRequested_NotModified<TContext>(
             Assert.Empty(body);
         }
 
-        public static async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
             var sendFileMock = new Mock<IHttpResponseBodyFeature>();
             sendFileMock
                 .Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
@@ -299,44 +275,38 @@ public static async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePr
 
             var httpContext = GetHttpContext();
             httpContext.Features.Set(sendFileMock.Object);
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
             sendFileMock.Verify();
         }
 
-        public static async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent<TContext>(
-            long? start,
-            long? end,
-            long contentLength,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData(0, 3, 4)]
+        [InlineData(8, 13, 6)]
+        [InlineData(null, 3, 3)]
+        [InlineData(8, null, 26)]
+        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, long contentLength)
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            result.EnableRangeProcessing = true;
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
-            var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.Range = new RangeHeaderValue(start, end);
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object functionContext = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)functionContext);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
-            start = start ?? 34 - end;
+            start ??= 34 - end;
             end = start + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")), sendFile.Name);
             Assert.Equal(start, sendFile.Offset);
             Assert.Equal(contentLength, sendFile.Length);
@@ -349,21 +319,18 @@ public static async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange
             Assert.Equal(contentLength, httpResponse.ContentLength);
         }
 
-        public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
         {
             // Arrange
             var expectedContentType = "text/foo; charset=us-ascii";
             var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt"));
-            var result = new TestPhysicalFileResult(path, expectedContentType);
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, expectedContentType);
 
             // Assert
             Assert.Equal(expectedContentType, httpContext.Response.ContentType);
@@ -373,22 +340,18 @@ public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<T
             Assert.Equal(CancellationToken.None, sendFile.Token);
         }
 
-        public static async Task ExecuteResultAsync_WorksWithAbsolutePaths<TContext>(
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_WorksWithAbsolutePaths()
         {
             // Arrange
             var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
-            var result = new TestPhysicalFileResult(path, "text/plain");
 
             var sendFile = new TestSendFileFeature();
             var httpContext = GetHttpContext();
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
 
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
             Assert.Equal(Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt")), sendFile.Name);
@@ -397,90 +360,63 @@ public static async Task ExecuteResultAsync_WorksWithAbsolutePaths<TContext>(
             Assert.Equal(CancellationToken.None, sendFile.Token);
         }
 
-        public static async Task ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths<TContext>(
-            string path,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("FilePathResultTestFile.txt")]
+        [InlineData("./FilePathResultTestFile.txt")]
+        [InlineData(".\\FilePathResultTestFile.txt")]
+        [InlineData("~/FilePathResultTestFile.txt")]
+        [InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
+        [InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
+        [InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
+        [InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
+        [InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
+        [InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
+        [InlineData("~/SubFolder/SubFolderTestFile.txt")]
+        [InlineData("~/SubFolder\\SubFolderTestFile.txt")]
+        public async Task ExecuteAsync_ThrowsNotSupported_ForNonRootedPaths(string path)
         {
             // Arrange
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            var actionContext = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
             var expectedMessage = $"Path '{path}' was not rooted.";
+            var httpContext = GetHttpContext();
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? actionContext.HttpContext : actionContext;
             var ex = await Assert.ThrowsAsync<NotSupportedException>(
-                () => function(result, (TContext)context));
+                () => ExecuteAsync(httpContext, path, "text/plain"));
 
             // Assert
             Assert.Equal(expectedMessage, ex.Message);
         }
 
-        public static void ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths<TContext>(
-            string path,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("/SubFolder/SubFolderTestFile.txt")]
+        [InlineData("\\SubFolder\\SubFolderTestFile.txt")]
+        [InlineData("/SubFolder\\SubFolderTestFile.txt")]
+        [InlineData("\\SubFolder/SubFolderTestFile.txt")]
+        [InlineData("./SubFolder/SubFolderTestFile.txt")]
+        [InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
+        [InlineData("./SubFolder\\SubFolderTestFile.txt")]
+        [InlineData(".\\SubFolder/SubFolderTestFile.txt")]
+        public void ExecuteAsync_ThrowsDirectoryNotFound_IfItCanNotFindTheDirectory_ForRootPaths(string path)
         {
             // Arrange
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
+            var httpContext = GetHttpContext();
 
             // Act & Assert
-            object context = typeof(TContext) == typeof(HttpContext) ? actionContext.HttpContext : actionContext;
             Assert.ThrowsAsync<DirectoryNotFoundException>(
-                () => function(result, (TContext)context));
+                () => ExecuteAsync(httpContext, path, "text/plain"));
         }
 
-        public static void ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths<TContext>(
-            string path,
-            Func<PhysicalFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("/FilePathResultTestFile.txt")]
+        [InlineData("\\FilePathResultTestFile.txt")]
+        public void ExecuteAsync_ThrowsFileNotFound_WhenFileDoesNotExist_ForRootPaths(string path)
         {
             // Arrange
-            var result = new TestPhysicalFileResult(path, "text/plain");
-            var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
+            var httpContext = GetHttpContext();
 
             // Act & Assert
-            object context = typeof(TContext) == typeof(HttpContext) ? actionContext.HttpContext : actionContext;
             Assert.ThrowsAsync<FileNotFoundException>(
-                () => function(result, (TContext)context));
-        }
-
-        private class TestPhysicalFileResult : PhysicalFileResult, IResult
-        {
-            public TestPhysicalFileResult(string filePath, string contentType)
-                : base(filePath, contentType)
-            {
-            }
-
-            public override Task ExecuteResultAsync(ActionContext context)
-            {
-                var executor = context.HttpContext.RequestServices.GetRequiredService<TestPhysicalFileResultExecutor>();
-                return executor.ExecuteAsync(context, this);
-            }
-
-            Task IResult.ExecuteAsync(HttpContext httpContext)
-            {
-                var lastModified = DateTimeOffset.MinValue.AddDays(1);
-                var fileLastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0));
-                return ExecuteAsyncInternal(httpContext, this, fileLastModified, 34);
-            }
-        }
-
-        private class TestPhysicalFileResultExecutor : PhysicalFileResultExecutor
-        {
-            public TestPhysicalFileResultExecutor(ILoggerFactory loggerFactory)
-                : base(loggerFactory)
-            {
-            }
-
-            protected override FileMetadata GetFileInfo(string path)
-            {
-                var lastModified = DateTimeOffset.MinValue.AddDays(1);
-                return new FileMetadata
-                {
-                    Exists = true,
-                    Length = 34,
-                    LastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0))
-                };
-            }
+                () => ExecuteAsync(httpContext, path, "text/plain"));
         }
 
         private class TestSendFileFeature : IHttpResponseBodyFeature
@@ -522,8 +458,8 @@ public Task StartAsync(CancellationToken cancellation = default)
         private static IServiceCollection CreateServices()
         {
             var services = new ServiceCollection();
-            services.AddSingleton<TestPhysicalFileResultExecutor>();
             services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
             return services;
         }
 
diff --git a/src/Mvc/Mvc.Core/test/BaseRedirectResultTest.cs b/src/Shared/ResultsTests/RedirectResultTestBase.cs
similarity index 50%
rename from src/Mvc/Mvc.Core/test/BaseRedirectResultTest.cs
rename to src/Shared/ResultsTests/RedirectResultTestBase.cs
index 6097a09263ec..7c459e9d9e74 100644
--- a/src/Mvc/Mvc.Core/test/BaseRedirectResultTest.cs
+++ b/src/Shared/ResultsTests/RedirectResultTestBase.cs
@@ -4,33 +4,30 @@
 using System;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Abstractions;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Mvc.Routing;
-using Microsoft.AspNetCore.Routing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
-using Moq;
+using Microsoft.Extensions.Logging.Abstractions;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Internal
 {
-    public class BaseRedirectResultTest
+    public abstract class RedirectResultTestBase
     {
-        public static async Task Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde<TContext>(
+        protected abstract Task ExecuteAsync(HttpContext httpContext, string contentPath);
+
+        [Theory]
+        [InlineData("", "/Home/About", "/Home/About")]
+        [InlineData("/myapproot", "/test", "/test")]
+        public async Task Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde<TContext>(
             string appRoot,
             string contentPath,
-            string expectedPath,
-            Func<RedirectResult, TContext, Task> function)
+            string expectedPath)
         {
             // Arrange
             var httpContext = GetHttpContext(appRoot);
-            var actionContext = GetActionContext(httpContext);
-            var result = new RedirectResult(contentPath);
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext) context);
+            await ExecuteAsync(httpContext, contentPath);
 
             // Assert
             // Verifying if Redirect was called with the specific Url and parameter flag.
@@ -38,20 +35,22 @@ public static async Task Execute_ReturnsContentPath_WhenItDoesNotStartWithTilde<
             Assert.Equal(StatusCodes.Status302Found, httpContext.Response.StatusCode);
         }
 
-        public static async Task Execute_ReturnsAppRelativePath_WhenItStartsWithTilde<TContext>(
+        [Theory]
+        [InlineData(null, "~/Home/About", "/Home/About")]
+        [InlineData("/", "~/Home/About", "/Home/About")]
+        [InlineData("/", "~/", "/")]
+        [InlineData("", "~/Home/About", "/Home/About")]
+        [InlineData("/myapproot", "~/", "/myapproot/")]
+        public async Task Execute_ReturnsAppRelativePath_WhenItStartsWithTilde(
             string appRoot,
             string contentPath,
-            string expectedPath,
-            Func<RedirectResult, TContext, Task> function)
+            string expectedPath)
         {
             // Arrange
             var httpContext = GetHttpContext(appRoot);
-            var actionContext = GetActionContext(httpContext);
-            var result = new RedirectResult(contentPath);
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, contentPath);
 
             // Assert
             // Verifying if Redirect was called with the specific Url and parameter flag.
@@ -59,23 +58,11 @@ public static async Task Execute_ReturnsAppRelativePath_WhenItStartsWithTilde<TC
             Assert.Equal(StatusCodes.Status302Found, httpContext.Response.StatusCode);
         }
 
-        private static ActionContext GetActionContext(HttpContext httpContext)
-        {
-            var routeData = new RouteData();
-            routeData.Routers.Add(Mock.Of<IRouter>());
-
-            return new ActionContext(
-                httpContext,
-                routeData,
-                new ActionDescriptor());
-        }
-
         private static IServiceProvider GetServiceProvider()
         {
             var serviceCollection = new ServiceCollection();
-            serviceCollection.AddSingleton<IActionResultExecutor<RedirectResult>, RedirectResultExecutor>();
-            serviceCollection.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
-            serviceCollection.AddTransient<ILoggerFactory, LoggerFactory>();
+            serviceCollection.AddTransient<ILoggerFactory, NullLoggerFactory>();
+            serviceCollection.AddTransient(typeof(ILogger<>), typeof(NullLogger<>));
             return serviceCollection.BuildServiceProvider();
         }
 
diff --git a/src/Mvc/Mvc.Core/test/BaseVirtualFileResultTest.cs b/src/Shared/ResultsTests/VirtualFileResultTestBase.cs
similarity index 50%
rename from src/Mvc/Mvc.Core/test/BaseVirtualFileResultTest.cs
rename to src/Shared/ResultsTests/VirtualFileResultTestBase.cs
index 727d18eadb8f..e224ff651a81 100644
--- a/src/Mvc/Mvc.Core/test/BaseVirtualFileResultTest.cs
+++ b/src/Shared/ResultsTests/VirtualFileResultTestBase.cs
@@ -10,9 +10,6 @@
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Mvc.Abstractions;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Routing;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.FileProviders;
 using Microsoft.Extensions.Logging;
@@ -21,49 +18,52 @@
 using Moq;
 using Xunit;
 
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Internal
 {
-    public class BaseVirtualFileResultTest
+    public abstract class VirtualFileResultTestBase
     {
-        public static async Task WriteFileAsync_WritesRangeRequested<TContext>(
+        protected abstract Task ExecuteAsync(
+            HttpContext httpContext,
+            string path,
+            string contentType,
+            DateTimeOffset? lastModified = null,
+            EntityTagHeaderValue entityTag = null,
+            bool enableRangeProcessing = false);
+
+        [Theory]
+        [InlineData(0, 3, 4)]
+        [InlineData(8, 13, 6)]
+        [InlineData(null, 4,  4)]
+        [InlineData(8, null, 25)]
+        public async Task WriteFileAsync_WritesRangeRequested<TContext>(
             long? start,
             long? end,
-            string expectedString,
-            long contentLength,
-            Func<VirtualFileResult, TContext, Task> function)
+            long contentLength)
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
+            
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.Range = new RangeHeaderValue(start, end);
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, enableRangeProcessing: true);
 
             // Assert
             var startResult = start ?? 33 - end;
             var endResult = startResult + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             var contentRange = new ContentRangeHeaderValue(startResult.Value, endResult.Value, 33);
             Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
             Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
@@ -75,41 +75,32 @@ public static async Task WriteFileAsync_WritesRangeRequested<TContext>(
             Assert.Equal((long?)contentLength, sendFileFeature.Length);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange()
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
 
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, entityTag: entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status206PartialContent, httpResponse.StatusCode);
             Assert.Equal("bytes", httpResponse.Headers.AcceptRanges);
             var contentRange = new ContentRangeHeaderValue(0, 3, 33);
@@ -121,40 +112,32 @@ public static async Task WriteFileAsync_IfRangeHeaderValid_WritesRequestedRange<
             Assert.Equal(4, sendFileFeature.Length);
         }
 
-        public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequestedIgnored()
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
 
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, entityTag: entityTag);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
             Assert.Equal(path, sendFileFeature.Name);
@@ -162,41 +145,32 @@ public static async Task WriteFileAsync_RangeProcessingNotEnabled_RangeRequested
             Assert.Null(sendFileFeature.Length);
         }
 
-        public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnored()
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
 
-            var entityTag = result.EntityTag = new EntityTagHeaderValue("\"Etag\"");
+            var entityTag = new EntityTagHeaderValue("\"Etag\"");
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
             requestHeaders.Range = new RangeHeaderValue(0, 3);
             requestHeaders.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"NotEtag\""));
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, entityTag: entityTag, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.Equal(entityTag.ToString(), httpResponse.Headers.ETag);
             Assert.Equal(path, sendFileFeature.Name);
@@ -204,40 +178,33 @@ public static async Task WriteFileAsync_IfRangeHeaderInvalid_RangeRequestedIgnor
             Assert.Null(sendFileFeature.Length);
         }
 
-        public static async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored<TContext>(
-            string rangeString,
-            Func<VirtualFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("0-5")]
+        [InlineData("bytes = ")]
+        [InlineData("bytes = 1-4, 5-11")]
+        public async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored(string rangeString)
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                     .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                    .AddSingleton(appEnvironment.Object)
-                    .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                    .AddTransient<ILoggerFactory, LoggerFactory>()
-                    .BuildServiceProvider();
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             httpContext.Request.Headers.Range = rangeString;
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
             Assert.Empty(httpResponse.Headers.ContentRange);
             Assert.NotEmpty(httpResponse.Headers.LastModified);
@@ -246,40 +213,32 @@ public static async Task WriteFileAsync_RangeHeaderMalformed_RangeRequestIgnored
             Assert.Null(sendFileFeature.Length);
         }
 
-        public static async Task WriteFileAsync_RangeRequestedNotSatisfiable<TContext>(
-            string rangeString,
-            Func<VirtualFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("bytes = 35-36")]
+        [InlineData("bytes = -0")]
+        public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                     .Returns(GetFileProvider(path));
 
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Response.Body = new MemoryStream();
-            httpContext.RequestServices = new ServiceCollection()
-                    .AddSingleton(appEnvironment.Object)
-                    .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                    .AddTransient<ILoggerFactory, LoggerFactory>()
-                    .BuildServiceProvider();
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             httpContext.Request.Headers.Range = rangeString;
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Method = HttpMethods.Get;
             httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             httpResponse.Body.Seek(0, SeekOrigin.Begin);
             var streamReader = new StreamReader(httpResponse.Body);
             var body = streamReader.ReadToEndAsync().Result;
@@ -292,39 +251,30 @@ public static async Task WriteFileAsync_RangeRequestedNotSatisfiable<TContext>(
             Assert.Empty(body);
         }
 
-        public static async Task WriteFileAsync_RangeRequested_PreconditionFailed<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeRequested_PreconditionFailed()
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue;
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status412PreconditionFailed, httpResponse.StatusCode);
             Assert.Null(httpResponse.ContentLength);
             Assert.Empty(httpResponse.Headers.ContentRange);
@@ -332,39 +282,31 @@ public static async Task WriteFileAsync_RangeRequested_PreconditionFailed<TConte
             Assert.Null(sendFileFeature.Name); // Not called
         }
 
-        public static async Task WriteFileAsync_RangeRequested_NotModified<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task WriteFileAsync_RangeRequested_NotModified()
         {
             // Arrange
             var path = Path.GetFullPath("helllo.txt");
             var contentType = "text/plain; charset=us-ascii; p1=p1-value";
-            var result = new TestVirtualFileResult(path, contentType);
-            result.EnableRangeProcessing = true;
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
+            
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.IfModifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Headers.Range = "bytes = 0-6";
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, contentType, enableRangeProcessing: true);
 
             // Assert
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(StatusCodes.Status304NotModified, httpResponse.StatusCode);
             Assert.Null(httpResponse.ContentLength);
             Assert.Empty(httpResponse.Headers.ContentRange);
@@ -373,105 +315,35 @@ public static async Task WriteFileAsync_RangeRequested_NotModified<TContext>(
             Assert.Null(sendFileFeature.Name); // Not called
         }
 
-        public static async Task ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
-        {
-            // Arrange
-            var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
-            var result = new TestVirtualFileResult(path, "text/plain");
-
-            var appEnvironment = new Mock<IWebHostEnvironment>();
-            appEnvironment.Setup(app => app.WebRootFileProvider)
-                .Returns(GetFileProvider(path));
-
-            var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
-            httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
-
-            // Assert
-            Assert.Equal(path, sendFileFeature.Name);
-            Assert.Equal(0, sendFileFeature.Offset);
-            Assert.Null(sendFileFeature.Length);
-        }
-
-        public static async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
-        {
-            // Arrange
-            var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
-            var result = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = GetFileProvider(path),
-            };
-
-            var sendFileMock = new Mock<IHttpResponseBodyFeature>();
-            sendFileMock
-                .Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None))
-                .Returns(Task.FromResult<int>(0));
-
-            var httpContext = GetHttpContext();
-            httpContext.Features.Set(sendFileMock.Object);
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
-            // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
-
-            // Assert
-            sendFileMock.Verify();
-        }
-        public static async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent<TContext>(
-            long? start,
-            long? end,
-            string expectedString,
-            long contentLength,
-            Func<VirtualFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData(0, 3, 4)]
+        [InlineData(8, 13, 6)]
+        [InlineData(null, 3, 3)]
+        [InlineData(8, null, 25)]
+        public async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange_IfIHttpSendFilePresent(long? start, long? end, long contentLength)
         {
             // Arrange
             var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
-            var result = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = GetFileProvider(path),
-                EnableRangeProcessing = true,
-            };
 
             var sendFile = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFile);
-            var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
             var appEnvironment = new Mock<IWebHostEnvironment>();
             appEnvironment.Setup(app => app.WebRootFileProvider)
                 .Returns(GetFileProvider(path));
-            httpContext.RequestServices = new ServiceCollection()
-                .AddSingleton(appEnvironment.Object)
-                .AddTransient<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>()
-                .AddTransient<ILoggerFactory, LoggerFactory>()
-                .BuildServiceProvider();
 
             var requestHeaders = httpContext.Request.GetTypedHeaders();
             requestHeaders.Range = new RangeHeaderValue(start, end);
             requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
             httpContext.Request.Method = HttpMethods.Get;
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object functionContext = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext) functionContext);
+            await ExecuteAsync(httpContext, path, "text/plain", enableRangeProcessing: true);
 
             // Assert
             start = start ?? 33 - end;
             end = start + contentLength - 1;
-            var httpResponse = actionContext.HttpContext.Response;
+            var httpResponse = httpContext.Response;
             Assert.Equal(Path.Combine("TestFiles", "FilePathResultTestFile.txt"), sendFile.Name);
             Assert.Equal(start, sendFile.Offset);
             Assert.Equal(contentLength, sendFile.Length);
@@ -484,112 +356,91 @@ public static async Task ExecuteResultAsync_CallsSendFileAsyncWithRequestedRange
             Assert.Equal(contentLength, httpResponse.ContentLength);
         }
 
-        public static async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
         {
             // Arrange
             var expectedContentType = "text/foo; charset=us-ascii";
-            var result = new TestVirtualFileResult(
-                "FilePathResultTestFile_ASCII.txt", expectedContentType)
-            {
-                FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"),
-            };
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider("FilePathResultTestFile_ASCII.txt"));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, "FilePathResultTestFile_ASCII.txt", expectedContentType);
 
             // Assert
             Assert.Equal(expectedContentType, httpContext.Response.ContentType);
             Assert.Equal("FilePathResultTestFile_ASCII.txt", sendFileFeature.Name);
         }
 
-        public static async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths()
         {
             // Arrange
             var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
-            var result = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = GetFileProvider(path),
-            };
 
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var httpContext = GetHttpContext(GetFileProvider(path));
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
             Assert.Equal(path, sendFileFeature.Name);
         }
 
-        public static async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths<TContext>(
-            string path,
-            Func<VirtualFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("FilePathResultTestFile.txt")]
+        [InlineData("TestFiles/FilePathResultTestFile.txt")]
+        [InlineData("TestFiles/../FilePathResultTestFile.txt")]
+        [InlineData("TestFiles\\FilePathResultTestFile.txt")]
+        [InlineData("TestFiles\\..\\FilePathResultTestFile.txt")]
+        [InlineData(@"\\..//?><|""&@#\c:\..\? /..txt")]
+        public async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(string path)
         {
             // Arrange
-            var result = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = GetFileProvider(path),
-            };
-
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var webRootFileProvider = GetFileProvider(path);
+            var httpContext = GetHttpContext(webRootFileProvider);
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
 
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
-            Mock.Get(result.FileProvider).Verify();
+            Mock.Get(webRootFileProvider).Verify();
             Assert.Equal(path, sendFileFeature.Name);
         }
 
-        public static async Task ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider<TContext>(
-            string path,
-            Func<VirtualFileResult, TContext, Task> function)
+        [Theory]
+        [InlineData("~/FilePathResultTestFile.txt")]
+        [InlineData("~/TestFiles/FilePathResultTestFile.txt")]
+        [InlineData("~/TestFiles/../FilePathResultTestFile.txt")]
+        [InlineData("~/TestFiles\\..\\FilePathResultTestFile.txt")]
+        [InlineData(@"~~~~\\..//?>~<|""&@#\c:\..\? /..txt~~~")]
+        public async Task ExecuteResultAsync_TrimsTilde_BeforeInvokingFileProvider(string path)
         {
             // Arrange
             var expectedPath = path.Substring(1);
-            var result = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = GetFileProvider(expectedPath),
-            };
-
             var sendFileFeature = new TestSendFileFeature();
-            var httpContext = GetHttpContext();
+            var webRootFileProvider = GetFileProvider(expectedPath);
+            var httpContext = GetHttpContext(webRootFileProvider);
             httpContext.Features.Set<IHttpResponseBodyFeature>(sendFileFeature);
 
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
-
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(result, (TContext)context);
+            await ExecuteAsync(httpContext, path, "text/plain");
 
             // Assert
-            Mock.Get(result.FileProvider).Verify();
+            Mock.Get(webRootFileProvider).Verify();
             Assert.Equal(expectedPath, sendFileFeature.Name);
         }
 
-        public static async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles()
         {
             // Arrange
-            var httpContext = GetHttpContext(typeof(VirtualFileResultExecutor));
-            httpContext.Response.Body = new MemoryStream();
-            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
             var expectedData = "This is an embedded resource";
             var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedData));
 
@@ -600,14 +451,11 @@ public static async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles<TContext>
             var nonDiskFileProvider = new Mock<IFileProvider>();
             nonDiskFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>())).Returns(nonDiskFileInfo.Object);
 
-            var filePathResult = new VirtualFileResult("/SampleEmbeddedFile.txt", "text/plain")
-            {
-                FileProvider = nonDiskFileProvider.Object
-            };
+            var httpContext = GetHttpContext(nonDiskFileProvider.Object);
+            httpContext.Response.Body = new MemoryStream();
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? httpContext : actionContext;
-            await function(filePathResult, (TContext)context);
+            await ExecuteAsync(httpContext, "/SampleEmbeddedFile.txt", "text/plain");
 
             // Assert
             httpContext.Response.Body.Position = 0;
@@ -615,8 +463,8 @@ public static async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles<TContext>
             Assert.Equal(expectedData, contents);
         }
 
-        public static async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile<TContext>(
-            Func<VirtualFileResult, TContext, Task> function)
+        [Fact]
+        public async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile()
         {
             // Arrange
             var path = "TestPath.txt";
@@ -624,44 +472,34 @@ public static async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCan
             fileInfo.SetupGet(f => f.Exists).Returns(false);
             var fileProvider = new Mock<IFileProvider>();
             fileProvider.Setup(f => f.GetFileInfo(path)).Returns(fileInfo.Object);
-            var filePathResult = new TestVirtualFileResult(path, "text/plain")
-            {
-                FileProvider = fileProvider.Object,
-            };
 
-            var expectedMessage = "Could not find file: " + path;
-            var actionContext = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
+            var expectedMessage = $"Could not find file: {path}.";
+            var httpContext = GetHttpContext(fileProvider.Object);
 
             // Act
-            object context = typeof(TContext) == typeof(HttpContext) ? actionContext.HttpContext : actionContext;
-            var ex = await Assert.ThrowsAsync<FileNotFoundException>(() => function(filePathResult, (TContext)context));
+            var ex = await Assert.ThrowsAsync<FileNotFoundException>(() => ExecuteAsync(httpContext, path, "text/plain"));
 
             // Assert
             Assert.Equal(expectedMessage, ex.Message);
             Assert.Equal(path, ex.FileName);
         }
 
-        private static IServiceCollection CreateServices(Type executorType)
+        private static IServiceCollection CreateServices(IFileProvider webRootFileProvider)
         {
             var services = new ServiceCollection();
 
-            var hostingEnvironment = new Mock<IWebHostEnvironment>();
+            var hostingEnvironment = Mock.Of<IWebHostEnvironment>(e => e.WebRootFileProvider == webRootFileProvider);
 
-            services.AddSingleton<IActionResultExecutor<VirtualFileResult>, TestVirtualFileResultExecutor>();
-            if (executorType != null)
-            {
-                services.AddSingleton(typeof(IActionResultExecutor<VirtualFileResult>), executorType);
-            }
-
-            services.AddSingleton<IWebHostEnvironment>(hostingEnvironment.Object);
+            services.AddSingleton<IWebHostEnvironment>(hostingEnvironment);
             services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+            services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
 
             return services;
         }
 
-        private static HttpContext GetHttpContext(Type executorType = null)
+        private static HttpContext GetHttpContext(IFileProvider webRootFileProvider)
         {
-            var services = CreateServices(executorType);
+            var services = CreateServices(webRootFileProvider);
 
             var httpContext = new DefaultHttpContext();
             httpContext.RequestServices = services.BuildServiceProvider();
@@ -669,7 +507,7 @@ private static HttpContext GetHttpContext(Type executorType = null)
             return httpContext;
         }
 
-        private static IFileProvider GetFileProvider(string path)
+        protected static IFileProvider GetFileProvider(string path)
         {
             var fileInfo = new Mock<IFileInfo>();
             fileInfo.SetupGet(fi => fi.Length).Returns(33);
@@ -686,28 +524,6 @@ private static IFileProvider GetFileProvider(string path)
             return fileProvider.Object;
         }
 
-        private class TestVirtualFileResult : VirtualFileResult
-        {
-            public TestVirtualFileResult(string filePath, string contentType)
-                : base(filePath, contentType)
-            {
-            }
-
-            public override Task ExecuteResultAsync(ActionContext context)
-            {
-                var executor = (TestVirtualFileResultExecutor)context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<VirtualFileResult>>();
-                return executor.ExecuteAsync(context, this);
-            }
-        }
-
-        private class TestVirtualFileResultExecutor : VirtualFileResultExecutor
-        {
-            public TestVirtualFileResultExecutor(ILoggerFactory loggerFactory, IWebHostEnvironment hostingEnvironment)
-                : base(loggerFactory, hostingEnvironment)
-            {
-            }
-        }
-
         private class TestSendFileFeature : IHttpResponseBodyFeature
         {
             public string Name { get; set; }

From 9f27fc6e253786244851d12a5b2e92a73e9bba12 Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Mon, 28 Jun 2021 10:22:36 -0700
Subject: [PATCH 2/6] Add some functional tests

---
 src/Http/Http.Results/src/ObjectResult.cs     |  7 +-
 src/Mvc/MvcNoDeps.slnf                        |  2 +
 .../SimpleWithWebApplicationBuilderTests.cs   | 68 ++++++++++++++++++-
 .../Program.cs                                | 23 ++++++-
 ...pleWebSiteWithWebApplicationBuilder.csproj |  2 +
 ...eWithWebApplicationBuilderException.csproj |  1 +
 6 files changed, 95 insertions(+), 8 deletions(-)

diff --git a/src/Http/Http.Results/src/ObjectResult.cs b/src/Http/Http.Results/src/ObjectResult.cs
index 679743da3684..e01a0f6ffd48 100644
--- a/src/Http/Http.Results/src/ObjectResult.cs
+++ b/src/Http/Http.Results/src/ObjectResult.cs
@@ -26,7 +26,7 @@ public ObjectResult(object? value, int statusCode)
         /// <summary>
         /// Gets or sets the HTTP status code.
         /// </summary>
-        public int? StatusCode { get; }
+        public int StatusCode { get; }
 
         public Task ExecuteAsync(HttpContext httpContext)
         {
@@ -34,10 +34,7 @@ public Task ExecuteAsync(HttpContext httpContext)
             var logger = loggerFactory.CreateLogger(GetType());
             Log.ObjectResultExecuting(logger, Value);
 
-            if (StatusCode is int statusCode)
-            {
-                httpContext.Response.StatusCode = statusCode;
-            }
+            httpContext.Response.StatusCode = StatusCode;
 
             OnFormatting(httpContext);
             return httpContext.Response.WriteAsJsonAsync(Value);
diff --git a/src/Mvc/MvcNoDeps.slnf b/src/Mvc/MvcNoDeps.slnf
index b393e2b59c4f..5bcf63dd8a47 100644
--- a/src/Mvc/MvcNoDeps.slnf
+++ b/src/Mvc/MvcNoDeps.slnf
@@ -64,6 +64,8 @@
       "src\\Mvc\\test\\WebSites\\RazorWebSite\\RazorWebSite.csproj",
       "src\\Mvc\\test\\WebSites\\RoutingWebSite\\Mvc.RoutingWebSite.csproj",
       "src\\Mvc\\test\\WebSites\\SecurityWebSite\\SecurityWebSite.csproj",
+      "src\\Mvc\\test\\WebSites\\SimpleWebSiteWithWebApplicationBuilderException\\SimpleWebSiteWithWebApplicationBuilderException.csproj",
+      "src\\Mvc\\test\\WebSites\\SimpleWebSiteWithWebApplicationBuilder\\SimpleWebSiteWithWebApplicationBuilder.csproj",
       "src\\Mvc\\test\\WebSites\\SimpleWebSite\\SimpleWebSite.csproj",
       "src\\Mvc\\test\\WebSites\\TagHelpersWebSite\\TagHelpersWebSite.csproj",
       "src\\Mvc\\test\\WebSites\\VersioningWebSite\\VersioningWebSite.csproj",
diff --git a/src/Mvc/test/Mvc.FunctionalTests/SimpleWithWebApplicationBuilderTests.cs b/src/Mvc/test/Mvc.FunctionalTests/SimpleWithWebApplicationBuilderTests.cs
index a677f34009d6..27fc44c124a5 100644
--- a/src/Mvc/test/Mvc.FunctionalTests/SimpleWithWebApplicationBuilderTests.cs
+++ b/src/Mvc/test/Mvc.FunctionalTests/SimpleWithWebApplicationBuilderTests.cs
@@ -1,7 +1,6 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System;
 using System.Net;
 using System.Net.Http;
 using System.Threading.Tasks;
@@ -30,5 +29,72 @@ public async Task HelloWorld()
             // Assert
             Assert.Equal(expected, content);
         }
+
+        [Fact]
+        public async Task JsonResult_Works()
+        {
+            // Arrange
+            var expected = "{\"name\":\"John\",\"age\":42}";
+
+            // Act
+            var response = await Client.GetAsync("/json");
+
+            // Assert
+            await response.AssertStatusCodeAsync(HttpStatusCode.OK);
+            var content = await response.Content.ReadAsStringAsync();
+            Assert.Equal(expected, content);
+        }
+
+        [Fact]
+        public async Task OkObjectResult_Works()
+        {
+            // Arrange
+            var expected = "{\"name\":\"John\",\"age\":42}";
+
+            // Act
+            var response = await Client.GetAsync("/ok-object");
+
+            // Assert
+            await response.AssertStatusCodeAsync(HttpStatusCode.OK);
+            var content = await response.Content.ReadAsStringAsync();
+            Assert.Equal(expected, content);
+        }
+
+        [Fact]
+        public async Task AcceptedObjectResult_Works()
+        {
+            // Arrange
+            var expected = "{\"name\":\"John\",\"age\":42}";
+
+            // Act
+            var response = await Client.GetAsync("/accepted-object");
+
+            // Assert
+            await response.AssertStatusCodeAsync(HttpStatusCode.Accepted);
+            Assert.Equal("/ok-object", response.Headers.Location.ToString());
+            var content = await response.Content.ReadAsStringAsync();
+            Assert.Equal(expected, content);
+        }
+
+        [Fact]
+        public async Task ActionReturningMoreThanOneResult_NotFound()
+        {
+            // Act
+            var response = await Client.GetAsync("/many-results?id=-1");
+
+            // Assert
+            await response.AssertStatusCodeAsync(HttpStatusCode.NotFound);
+        }
+
+        [Fact]
+        public async Task ActionReturningMoreThanOneResult_Found()
+        {
+            // Act
+            var response = await Client.GetAsync("/many-results?id=7");
+
+            // Assert
+            await response.AssertStatusCodeAsync(HttpStatusCode.MovedPermanently);
+            Assert.Equal("/json", response.Headers.Location.ToString());
+        }
     }
 }
diff --git a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/Program.cs b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/Program.cs
index 09c9e72998ab..e185a78c7db9 100644
--- a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/Program.cs
+++ b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/Program.cs
@@ -1,11 +1,30 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
-using System;
 using Microsoft.AspNetCore.Builder;
+using static Microsoft.AspNetCore.Http.Results;
 
 var app = WebApplication.Create(args);
 
-app.MapGet("/", (Func<string>)(() => "Hello World"));
+app.MapGet("/", () => "Hello World");
+
+app.MapGet("/json", () => Json(new Person("John", 42)));
+
+app.MapGet("/ok-object", () => Ok(new Person("John", 42)));
+
+app.MapGet("/accepted-object", () => Accepted("/ok-object", new Person("John", 42)));
+
+app.MapGet("/many-results", (int id) =>
+{
+    if (id == -1)
+    {
+        return NotFound();
+    }
+
+    return RedirectPermanent("/json");
+});
 
 app.Run();
+
+
+record Person(string Name, int Age);
diff --git a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/SimpleWebSiteWithWebApplicationBuilder.csproj b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/SimpleWebSiteWithWebApplicationBuilder.csproj
index 58eb21f2db2d..6a226b6d428f 100644
--- a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/SimpleWebSiteWithWebApplicationBuilder.csproj
+++ b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilder/SimpleWebSiteWithWebApplicationBuilder.csproj
@@ -2,9 +2,11 @@
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+    <LangVersion>Preview</LangVersion>
   </PropertyGroup>
 
   <ItemGroup>
     <Reference Include="Microsoft.AspNetCore" />
+    <Reference Include="Microsoft.AspNetCore.Http.Results" />
   </ItemGroup>
 </Project>
diff --git a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilderException/SimpleWebSiteWithWebApplicationBuilderException.csproj b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilderException/SimpleWebSiteWithWebApplicationBuilderException.csproj
index 58eb21f2db2d..b98dad5ccb6f 100644
--- a/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilderException/SimpleWebSiteWithWebApplicationBuilderException.csproj
+++ b/src/Mvc/test/WebSites/SimpleWebSiteWithWebApplicationBuilderException/SimpleWebSiteWithWebApplicationBuilderException.csproj
@@ -2,6 +2,7 @@
 
   <PropertyGroup>
     <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+    <LangVersion>Preview</LangVersion>
   </PropertyGroup>
 
   <ItemGroup>

From 53204a0734d8823617a46c7091845a8fedb35b9e Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Mon, 28 Jun 2021 11:53:16 -0700
Subject: [PATCH 3/6] Fixups

---
 src/Framework/test/TestData.cs      | 2 ++
 src/Mvc/Mvc.Core/src/Resources.resx | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/Framework/test/TestData.cs b/src/Framework/test/TestData.cs
index dd11b6d25171..ea48ec61ad4b 100644
--- a/src/Framework/test/TestData.cs
+++ b/src/Framework/test/TestData.cs
@@ -55,6 +55,7 @@ static TestData()
                 "Microsoft.AspNetCore.Http.Connections.Common",
                 "Microsoft.AspNetCore.Http.Extensions",
                 "Microsoft.AspNetCore.Http.Features",
+                "Microsoft.AspNetCore.Http.Results",
                 "Microsoft.AspNetCore.HttpLogging",
                 "Microsoft.AspNetCore.HttpOverrides",
                 "Microsoft.AspNetCore.HttpsPolicy",
@@ -188,6 +189,7 @@ static TestData()
                 { "Microsoft.AspNetCore.Http.Connections.Common", "6.0.0.0" },
                 { "Microsoft.AspNetCore.Http.Extensions", "6.0.0.0" },
                 { "Microsoft.AspNetCore.Http.Features", "6.0.0.0" },
+                { "Microsoft.AspNetCore.Http.Results", "6.0.0.0" },
                 { "Microsoft.AspNetCore.HttpLogging", "6.0.0.0" },
                 { "Microsoft.AspNetCore.HttpOverrides", "6.0.0.0" },
                 { "Microsoft.AspNetCore.HttpsPolicy", "6.0.0.0" },
diff --git a/src/Mvc/Mvc.Core/src/Resources.resx b/src/Mvc/Mvc.Core/src/Resources.resx
index a1e872c5be9b..44b27197e359 100644
--- a/src/Mvc/Mvc.Core/src/Resources.resx
+++ b/src/Mvc/Mvc.Core/src/Resources.resx
@@ -237,7 +237,7 @@
     <comment>0 is the newline - 1 is a newline separate list of action display names</comment>
   </data>
   <data name="FileResult_InvalidPath" xml:space="preserve">
-    <value>Could not find file: {0}</value>
+    <value>Could not find file: {0}.</value>
     <comment>{0} is the value for the provided path</comment>
   </data>
   <data name="SerializableError_DefaultError" xml:space="preserve">

From a5f07c085a5fc78560225e14ebbe44c2a2cf08e3 Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Mon, 28 Jun 2021 11:54:03 -0700
Subject: [PATCH 4/6] Fix typo

---
 .../src/{PhyiscalFileResult.cs => PhysicalFileResult.cs}          | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/Http/Http.Results/src/{PhyiscalFileResult.cs => PhysicalFileResult.cs} (100%)

diff --git a/src/Http/Http.Results/src/PhyiscalFileResult.cs b/src/Http/Http.Results/src/PhysicalFileResult.cs
similarity index 100%
rename from src/Http/Http.Results/src/PhyiscalFileResult.cs
rename to src/Http/Http.Results/src/PhysicalFileResult.cs

From 85fc7cbda9e93c1d9fa270e54d1e7e175ecb0e35 Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Mon, 28 Jun 2021 13:45:48 -0700
Subject: [PATCH 5/6] Apply suggestions from code review

Co-authored-by: Martin Costello <martin@martincostello.com>
---
 src/Http/Http.Results/src/AcceptedAtRouteResult.cs | 4 ++--
 src/Http/Http.Results/src/ChallengeResult.cs       | 2 +-
 src/Http/Http.Results/src/PhysicalFileResult.cs    | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
index 9d268a1fde93..8159803bccce 100644
--- a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
+++ b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
@@ -38,12 +38,12 @@ public AcceptedAtRouteResult(
         }
 
         /// <summary>
-        /// Gets or sets the name of the route to use for generating the URL.
+        /// Gets the name of the route to use for generating the URL.
         /// </summary>
         public string? RouteName { get; }
 
         /// <summary>
-        /// Gets or sets the route data to use for generating the URL.
+        /// Gets the route data to use for generating the URL.
         /// </summary>
         public RouteValueDictionary RouteValues { get; }
 
diff --git a/src/Http/Http.Results/src/ChallengeResult.cs b/src/Http/Http.Results/src/ChallengeResult.cs
index 7f4f16323140..e3614755e937 100644
--- a/src/Http/Http.Results/src/ChallengeResult.cs
+++ b/src/Http/Http.Results/src/ChallengeResult.cs
@@ -113,7 +113,7 @@ public static void ChallengeResultExecuting(ILogger logger, IList<string> authen
                 }
             }
 
-            [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting")]
+            [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
             private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
         }
     }
diff --git a/src/Http/Http.Results/src/PhysicalFileResult.cs b/src/Http/Http.Results/src/PhysicalFileResult.cs
index ed4bed6aa477..9a53b91e9466 100644
--- a/src/Http/Http.Results/src/PhysicalFileResult.cs
+++ b/src/Http/Http.Results/src/PhysicalFileResult.cs
@@ -42,7 +42,7 @@ public Task ExecuteAsync(HttpContext httpContext)
             var fileInfo = GetFileInfoWrapper(FileName);
             if (!fileInfo.Exists)
             {
-                throw new FileNotFoundException($"Could not find file: {FileName}");
+                throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
             }
 
             var logger = httpContext.RequestServices.GetRequiredService<ILogger<PhysicalFileResult>>();

From b330b73a774d0ecfc3a62a3a361c5d4270aff6e2 Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Mon, 28 Jun 2021 16:53:31 -0700
Subject: [PATCH 6/6] Clean up docs

---
 src/Http/Http.Results/src/Results.cs | 474 +++++++++++++++------------
 1 file changed, 268 insertions(+), 206 deletions(-)

diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs
index 467d5c4afab1..5b4162c924ea 100644
--- a/src/Http/Http.Results/src/Results.cs
+++ b/src/Http/Http.Results/src/Results.cs
@@ -13,105 +13,103 @@
 namespace Microsoft.AspNetCore.Http
 {
     /// <summary>
-    ///
+    /// A factory for <see cref="IResult"/>.
     /// </summary>
     public static class Results
     {
         #region SignIn / SignOut / Challenge
 
         /// <summary>
-        /// Creates a <see cref="ChallengeResult"/>.
-        /// </summary>
-        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
-        /// <remarks>
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext)"/>.
+        /// <para>
         /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
         /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
         /// are among likely status results.
-        /// </remarks>
+        /// </para>
+        /// </summary>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Challenge()
             => new ChallengeResult();
 
         /// <summary>
-        /// Creates a <see cref="ChallengeResult"/> with the specified authentication schemes.
-        /// </summary>
-        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
-        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
-        /// <remarks>
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, string?)"/>.
+        /// <para>
         /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
         /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
         /// are among likely status results.
-        /// </remarks>
+        /// </para>
+        /// </summary>
+        /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Challenge(params string[] authenticationSchemes)
             => new ChallengeResult { AuthenticationSchemes = authenticationSchemes };
 
         /// <summary>
-        /// Creates a <see cref="ChallengeResult"/> with the specified <paramref name="properties" />.
-        /// </summary>
-        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
-        /// challenge.</param>
-        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
-        /// <remarks>
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, AuthenticationProperties?)" />.
+        /// <para>
         /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
         /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
         /// are among likely status results.
-        /// </remarks>
+        /// </para>
+        /// </summary>
+        /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+        /// challenge.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Challenge(AuthenticationProperties properties)
             => new ChallengeResult { Properties = properties };
 
         /// <summary>
-        /// Creates a <see cref="ChallengeResult"/> with the specified authentication schemes and
-        /// <paramref name="properties" />.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, string?, AuthenticationProperties?)" />.
+        /// <para>
+        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+        /// are among likely status results.
+        /// </para>
         /// </summary>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
         /// challenge.</param>
         /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
-        /// <returns>The created <see cref="ChallengeResult"/> for the response.</returns>
-        /// <remarks>
-        /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
-        /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
-        /// are among likely status results.
-        /// </remarks>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Challenge(
             AuthenticationProperties properties,
             params string[] authenticationSchemes)
             => new ChallengeResult { AuthenticationSchemes = authenticationSchemes, Properties = properties };
 
         /// <summary>
-        /// Creates a <see cref="SignInResult"/>.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, ClaimsPrincipal)"/>.
         /// </summary>
         /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
-        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignIn(ClaimsPrincipal principal)
             => new SignInResult(principal);
 
         /// <summary>
-        /// Creates a <see cref="SignInResult"/> with the specified authentication scheme.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, string?, ClaimsPrincipal)" />.
         /// </summary>
         /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
         /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
-        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignIn(ClaimsPrincipal principal, string authenticationScheme)
             => new SignInResult(authenticationScheme, principal);
 
         /// <summary>
-        /// Creates a <see cref="SignInResult"/> with <paramref name="properties"/>.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, ClaimsPrincipal, AuthenticationProperties?)" />.
         /// </summary>
         /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
-        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignIn(
             ClaimsPrincipal principal,
             AuthenticationProperties properties)
             => new SignInResult(principal, properties);
 
         /// <summary>
-        /// Creates a <see cref="SignInResult"/> with the specified authentication scheme and
-        /// <paramref name="properties" />.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, string?, ClaimsPrincipal, AuthenticationProperties?)" />.
         /// </summary>
         /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
         /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
-        /// <returns>The created <see cref="SignInResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignIn(
             ClaimsPrincipal principal,
             AuthenticationProperties properties,
@@ -119,42 +117,41 @@ public static IResult SignIn(
             => new SignInResult(authenticationScheme, principal, properties);
 
         /// <summary>
-        /// Creates a <see cref="SignOutResult"/>.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext)" />.
         /// </summary>
-        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignOut()
             => new SignOutResult();
 
         /// <summary>
-        /// Creates a <see cref="SignOutResult"/> with <paramref name="properties"/>.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext, AuthenticationProperties?)" />.
         /// </summary>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
-        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignOut(AuthenticationProperties properties)
             => new SignOutResult(properties);
 
         /// <summary>
-        /// Creates a <see cref="SignOutResult"/> with the specified authentication schemes.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext, string?)" />.
         /// </summary>
         /// <param name="authenticationSchemes">The authentication schemes to use for the sign-out operation.</param>
-        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignOut(params string[] authenticationSchemes)
             => new SignOutResult(authenticationSchemes);
 
         /// <summary>
-        /// Creates a <see cref="SignOutResult"/> with the specified authentication schemes and
-        /// <paramref name="properties" />.
+        /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext, string?, AuthenticationProperties?)" />.
         /// </summary>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
         /// <param name="authenticationSchemes">The authentication scheme to use for the sign-out operation.</param>
-        /// <returns>The created <see cref="SignOutResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes)
             => new SignOutResult(authenticationSchemes, properties);
         #endregion
 
         #region ContentResult
         /// <summary>
-        /// Creates a <see cref="IResult"/> object by specifying a <paramref name="content"/> string.
+        /// Writes the <paramref name="content"/> string to the HTTP response.
         /// </summary>
         /// <param name="content">The content to write to the response.</param>
         /// <returns>The created <see cref="IResult"/> object for the response.</returns>
@@ -162,8 +159,7 @@ public static IResult Content(string content)
             => Content(content, (MediaTypeHeaderValue?)null);
 
         /// <summary>
-        /// Creates a <see cref="ContentResult"/> object by specifying a
-        /// <paramref name="content"/> string and a content type.
+        /// Writes the <paramref name="content"/> string to the HTTP response.
         /// </summary>
         /// <param name="content">The content to write to the response.</param>
         /// <param name="contentType">The content type (MIME type).</param>
@@ -172,8 +168,7 @@ public static IResult Content(string content, string contentType)
             => Content(content, MediaTypeHeaderValue.Parse(contentType));
 
         /// <summary>
-        /// Creates a <see cref="IResult"/> object by specifying a
-        /// <paramref name="content"/> string, a <paramref name="contentType"/>, and <paramref name="contentEncoding"/>.
+        /// Writes the <paramref name="content"/> string to the HTTP response.
         /// </summary>
         /// <param name="content">The content to write to the response.</param>
         /// <param name="contentType">The content type (MIME type).</param>
@@ -191,8 +186,7 @@ public static IResult Content(string content, string contentType, Encoding conte
         }
 
         /// <summary>
-        /// Creates a <see cref="IResult"/> object by specifying a
-        /// <paramref name="content"/> string and a <paramref name="contentType"/>.
+        /// Writes the <paramref name="content"/> string to the HTTP response.
         /// </summary>
         /// <param name="content">The content to write to the response.</param>
         /// <param name="contentType">The content type (MIME type).</param>
@@ -209,22 +203,25 @@ public static IResult Content(string content, MediaTypeHeaderValue? contentType)
 
         #region ForbidResult
         /// <summary>
-        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default).
+        /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext)"/>.
+        /// <para>
+        /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
+        /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
+        /// </para>
         /// </summary>
-        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
-        /// <remarks>
-        /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
-        /// a redirect to show a login page.
-        /// </remarks>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Forbid()
             => new ForbidResult();
 
         /// <summary>
-        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
-        /// specified authentication schemes.
+        /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext, string?)"/>.
+        /// <para>
+        /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
+        /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
+        /// </para>
         /// </summary>
         /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
-        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
         /// a redirect to show a login page.
@@ -233,12 +230,15 @@ public static IResult Forbid(params string[] authenticationSchemes)
             => new ForbidResult { AuthenticationSchemes = authenticationSchemes };
 
         /// <summary>
-        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
-        /// specified <paramref name="properties" />.
+        /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext, AuthenticationProperties?)"/>.
+        /// <para>
+        /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
+        /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
+        /// </para>
         /// </summary>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
         /// challenge.</param>
-        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
         /// a redirect to show a login page.
@@ -247,13 +247,16 @@ public static IResult Forbid(AuthenticationProperties properties)
             => new ForbidResult { Properties = properties };
 
         /// <summary>
-        /// Creates a <see cref="ForbidResult"/> (<see cref="StatusCodes.Status403Forbidden"/> by default) with the
-        /// specified authentication schemes and <paramref name="properties" />.
+        /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext, string?, AuthenticationProperties?)"/>.
+        /// <para>
+        /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
+        /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
+        /// </para>
         /// </summary>
         /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
         /// challenge.</param>
         /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
-        /// <returns>The created <see cref="ForbidResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
         /// a redirect to show a login page.
@@ -263,8 +266,7 @@ public static IResult Forbid(AuthenticationProperties properties, params string[
         #endregion
 
         /// <summary>
-        /// Creates a <see cref="JsonResult"/> object that serializes the specified <paramref name="data"/> object
-        /// to JSON.
+        /// Creates a <see cref="IResult"/> that serializes the specified <paramref name="data"/> object to JSON.
         /// </summary>
         /// <param name="data">The object to write as JSON.</param>
         /// <param name="options">The serializer options use when serializing the value.</param>
@@ -289,52 +291,60 @@ public static IResult Json(object? data, JsonSerializerOptions? options = null,
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType)
             => File(fileContents, contentType, fileDownloadName: null);
 
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, bool enableRangeProcessing)
             => File(fileContents, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
 
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName)
             => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName };
 
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, bool enableRangeProcessing)
             => new FileContentResult(fileContents, contentType)
             {
@@ -345,14 +355,16 @@ public static IResult File(byte[] fileContents, string contentType, string? file
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new FileContentResult(fileContents, contentType)
@@ -365,15 +377,17 @@ public static IResult File(byte[] fileContents, string contentType, DateTimeOffs
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new FileContentResult(fileContents, contentType)
@@ -387,15 +401,17 @@ public static IResult File(byte[] fileContents, string contentType, DateTimeOffs
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new FileContentResult(fileContents, contentType)
@@ -409,8 +425,10 @@ public static IResult File(byte[] fileContents, string contentType, string? file
         /// <summary>
         /// Returns a file with the specified <paramref name="fileContents" /> as content (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileContents">The file contents.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
@@ -418,7 +436,7 @@ public static IResult File(byte[] fileContents, string contentType, string? file
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileContentResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(byte[] fileContents, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new FileContentResult(fileContents, contentType)
@@ -435,12 +453,14 @@ public static IResult File(byte[] fileContents, string contentType, string? file
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -450,13 +470,15 @@ public static IResult File(Stream fileStream, string contentType)
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -467,13 +489,15 @@ public static IResult File(Stream fileStream, string contentType, bool enableRan
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -484,14 +508,16 @@ public static IResult File(Stream fileStream, string contentType, string? fileDo
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -505,14 +531,16 @@ public static IResult File(Stream fileStream, string contentType, string? fileDo
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -528,15 +556,17 @@ public static IResult File(Stream fileStream, string contentType, DateTimeOffset
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>),
         /// and the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -553,15 +583,17 @@ public static IResult File(Stream fileStream, string contentType, DateTimeOffset
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -578,8 +610,10 @@ public static IResult File(Stream fileStream, string contentType, string? fileDo
         /// <summary>
         /// Returns a file in the specified <paramref name="fileStream" /> (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
@@ -587,7 +621,7 @@ public static IResult File(Stream fileStream, string contentType, string? fileDo
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="FileStreamResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         /// <remarks>
         /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
         /// </remarks>
@@ -607,25 +641,29 @@ public static IResult File(Stream fileStream, string contentType, string? fileDo
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType)
             => PhysicalFile(physicalPath, contentType, fileDownloadName: null);
 
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType, bool enableRangeProcessing)
             => PhysicalFile(physicalPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
 
@@ -633,13 +671,15 @@ public static IResult PhysicalFile(string physicalPath, string contentType, bool
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(
             string physicalPath,
             string contentType,
@@ -650,14 +690,16 @@ public static IResult PhysicalFile(
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(
             string physicalPath,
             string contentType,
@@ -672,14 +714,16 @@ public static IResult PhysicalFile(
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), and
         /// the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new PhysicalFileResult(physicalPath, contentType)
@@ -692,15 +736,17 @@ public static IResult PhysicalFile(string physicalPath, string contentType, Date
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), and
         /// the specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new PhysicalFileResult(physicalPath, contentType)
@@ -714,15 +760,17 @@ public static IResult PhysicalFile(string physicalPath, string contentType, Date
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new PhysicalFileResult(physicalPath, contentType)
@@ -736,8 +784,10 @@ public static IResult PhysicalFile(string physicalPath, string contentType, stri
         /// <summary>
         /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>), the
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="physicalPath">The path to the file. The path must be an absolute path.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
@@ -745,7 +795,7 @@ public static IResult PhysicalFile(string physicalPath, string contentType, stri
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="PhysicalFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult PhysicalFile(string physicalPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new PhysicalFileResult(physicalPath, contentType)
@@ -762,25 +812,29 @@ public static IResult PhysicalFile(string physicalPath, string contentType, stri
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType)
             => File(virtualPath, contentType, fileDownloadName: null);
 
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, bool enableRangeProcessing)
             => File(virtualPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing);
 
@@ -788,13 +842,15 @@ public static IResult File(string virtualPath, string contentType, bool enableRa
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, string? fileDownloadName)
             => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName };
 
@@ -802,14 +858,16 @@ public static IResult File(string virtualPath, string contentType, string? fileD
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>) with the
         /// specified <paramref name="contentType" /> as the Content-Type and the
         /// specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, string? fileDownloadName, bool enableRangeProcessing)
             => new VirtualFileResult(virtualPath, contentType)
             {
@@ -820,14 +878,16 @@ public static IResult File(string virtualPath, string contentType, string? fileD
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), and the 
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new VirtualFileResult(virtualPath, contentType)
@@ -840,15 +900,17 @@ public static IResult File(string virtualPath, string contentType, DateTimeOffse
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), and the 
         /// specified <paramref name="contentType" /> as the Content-Type.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new VirtualFileResult(virtualPath, contentType)
@@ -862,15 +924,17 @@ public static IResult File(string virtualPath, string contentType, DateTimeOffse
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), the 
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
         /// <param name="fileDownloadName">The suggested file name.</param>
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag)
         {
             return new VirtualFileResult(virtualPath, contentType)
@@ -884,8 +948,10 @@ public static IResult File(string virtualPath, string contentType, string? fileD
         /// <summary>
         /// Returns the file specified by <paramref name="virtualPath" /> (<see cref="StatusCodes.Status200OK"/>), the 
         /// specified <paramref name="contentType" /> as the Content-Type, and the specified <paramref name="fileDownloadName" /> as the suggested file name.
+        /// <para>
         /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
         /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+        /// </para>
         /// </summary>
         /// <param name="virtualPath">The virtual path of the file to be returned.</param>
         /// <param name="contentType">The Content-Type of the file.</param>
@@ -893,7 +959,7 @@ public static IResult File(string virtualPath, string contentType, string? fileD
         /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
         /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
         /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
-        /// <returns>The created <see cref="VirtualFileResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult File(string virtualPath, string contentType, string? fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing)
         {
             return new VirtualFileResult(virtualPath, contentType)
@@ -908,11 +974,11 @@ public static IResult File(string virtualPath, string contentType, string? fileD
 
         #region RedirectResult variants
         /// <summary>
-        /// Creates a <see cref="RedirectResult"/> object that redirects (<see cref="StatusCodes.Status302Found"/>)
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>)
         /// to the specified <paramref name="url"/>.
         /// </summary>
         /// <param name="url">The URL to redirect to.</param>
-        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Redirect(string url)
         {
             if (string.IsNullOrEmpty(url))
@@ -924,11 +990,11 @@ public static IResult Redirect(string url)
         }
 
         /// <summary>
-        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to true
-        /// (<see cref="StatusCodes.Status301MovedPermanently"/>) using the specified <paramref name="url"/>.
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently" />)
+        /// to the specified <paramref name="url"/>.
         /// </summary>
         /// <param name="url">The URL to redirect to.</param>
-        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectPermanent(string url)
         {
             if (string.IsNullOrEmpty(url))
@@ -940,12 +1006,11 @@ public static IResult RedirectPermanent(string url)
         }
 
         /// <summary>
-        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to false
-        /// and <see cref="RedirectResult.PreserveMethod"/> set to true (<see cref="StatusCodes.Status307TemporaryRedirect"/>) 
-        /// using the specified <paramref name="url"/>.
+        /// Redirects (<see cref="StatusCodes.Status307TemporaryRedirect" />)
+        /// to the specified <paramref name="url"/> preserving the HTTP method.
         /// </summary>
         /// <param name="url">The URL to redirect to.</param>
-        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectPreserveMethod(string url)
         {
             if (string.IsNullOrEmpty(url))
@@ -957,12 +1022,11 @@ public static IResult RedirectPreserveMethod(string url)
         }
 
         /// <summary>
-        /// Creates a <see cref="RedirectResult"/> object with <see cref="RedirectResult.Permanent"/> set to true
-        /// and <see cref="RedirectResult.PreserveMethod"/> set to true (<see cref="StatusCodes.Status308PermanentRedirect"/>) 
-        /// using the specified <paramref name="url"/>.
+        /// Redirects (<see cref="StatusCodes.Status308PermanentRedirect" />)
+        /// to the specified <paramref name="url"/> preserving the HTTP method.
         /// </summary>
         /// <param name="url">The URL to redirect to.</param>
-        /// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectPermanentPreserveMethod(string url)
         {
             if (string.IsNullOrEmpty(url))
@@ -974,11 +1038,11 @@ public static IResult RedirectPermanentPreserveMethod(string url)
         }
 
         /// <summary>
-        /// Creates a <see cref="LocalRedirectResult"/> object that redirects 
-        /// (<see cref="StatusCodes.Status302Found"/>) to the specified local <paramref name="localUrl"/>.
+        /// Redirects (<see cref="StatusCodes.Status302Found"/>)
+        /// to the specified <paramref name="localUrl"/>.
         /// </summary>
         /// <param name="localUrl">The local URL to redirect to.</param>
-        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult LocalRedirect(string localUrl)
         {
             if (string.IsNullOrEmpty(localUrl))
@@ -990,11 +1054,11 @@ public static IResult LocalRedirect(string localUrl)
         }
 
         /// <summary>
-        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
-        /// true (<see cref="StatusCodes.Status301MovedPermanently"/>) using the specified <paramref name="localUrl"/>.
+        /// Redirects (<see cref="StatusCodes.Status301MovedPermanently" />)
+        /// to the specified <paramref name="localUrl"/>.
         /// </summary>
         /// <param name="localUrl">The local URL to redirect to.</param>
-        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult LocalRedirectPermanent(string localUrl)
         {
             if (string.IsNullOrEmpty(localUrl))
@@ -1006,12 +1070,11 @@ public static IResult LocalRedirectPermanent(string localUrl)
         }
 
         /// <summary>
-        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
-        /// false and <see cref="LocalRedirectResult.PreserveMethod"/> set to true 
-        /// (<see cref="StatusCodes.Status307TemporaryRedirect"/>) using the specified <paramref name="localUrl"/>.
+        /// Redirects (<see cref="StatusCodes.Status307TemporaryRedirect" />)
+        /// to the specified <paramref name="localUrl"/> preserving the HTTP method.
         /// </summary>
         /// <param name="localUrl">The local URL to redirect to.</param>
-        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult LocalRedirectPreserveMethod(string localUrl)
         {
             if (string.IsNullOrEmpty(localUrl))
@@ -1023,12 +1086,11 @@ public static IResult LocalRedirectPreserveMethod(string localUrl)
         }
 
         /// <summary>
-        /// Creates a <see cref="LocalRedirectResult"/> object with <see cref="LocalRedirectResult.Permanent"/> set to
-        /// true and <see cref="LocalRedirectResult.PreserveMethod"/> set to true 
-        /// (<see cref="StatusCodes.Status308PermanentRedirect"/>) using the specified <paramref name="localUrl"/>.
+        /// Redirects (<see cref="StatusCodes.Status308PermanentRedirect" />)
+        /// to the specified <paramref name="localUrl"/> preserving the HTTP method.
         /// </summary>
         /// <param name="localUrl">The local URL to redirect to.</param>
-        /// <returns>The created <see cref="LocalRedirectResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult LocalRedirectPermanentPreserveMethod(string localUrl)
         {
             if (string.IsNullOrEmpty(localUrl))
@@ -1043,7 +1105,7 @@ public static IResult LocalRedirectPermanentPreserveMethod(string localUrl)
         /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified <paramref name="routeName"/>.
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoute(string? routeName)
             => RedirectToRoute(routeName, routeValues: null);
 
@@ -1051,7 +1113,7 @@ public static IResult RedirectToRoute(string? routeName)
         /// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified route using the specified <paramref name="routeValues"/>.
         /// </summary>
         /// <param name="routeValues">The parameters for a route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoute(object? routeValues)
             => RedirectToRoute(routeName: null, routeValues: routeValues);
 
@@ -1061,7 +1123,7 @@ public static IResult RedirectToRoute(object? routeValues)
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The parameters for a route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoute(string? routeName, object? routeValues)
             => RedirectToRoute(routeName, routeValues, fragment: null);
 
@@ -1071,7 +1133,7 @@ public static IResult RedirectToRoute(string? routeName, object? routeValues)
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoute(string? routeName, string? fragment)
             => RedirectToRoute(routeName, routeValues: null, fragment: fragment);
 
@@ -1082,7 +1144,7 @@ public static IResult RedirectToRoute(string? routeName, string? fragment)
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The parameters for a route.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoute(
             string? routeName,
             object? routeValues,
@@ -1099,7 +1161,7 @@ public static IResult RedirectToRoute(
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>       
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>       
         public static IResult RedirectToRoutePreserveMethod(
             string? routeName = null,
             object? routeValues = null,
@@ -1118,7 +1180,7 @@ public static IResult RedirectToRoutePreserveMethod(
         /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeName"/>.
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoutePermanent(string? routeName)
             => RedirectToRoutePermanent(routeName, routeValues: null);
 
@@ -1127,7 +1189,7 @@ public static IResult RedirectToRoutePermanent(string? routeName)
         /// <see cref="RedirectToRouteResult.Permanent"/> set to true using the specified <paramref name="routeValues"/>.
         /// </summary>
         /// <param name="routeValues">The parameters for a route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoutePermanent(object? routeValues)
             => RedirectToRoutePermanent(routeName: null, routeValues: routeValues);
 
@@ -1138,7 +1200,7 @@ public static IResult RedirectToRoutePermanent(object? routeValues)
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The parameters for a route.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoutePermanent(string? routeName, object? routeValues)
             => RedirectToRoutePermanent(routeName, routeValues, fragment: null);
 
@@ -1149,7 +1211,7 @@ public static IResult RedirectToRoutePermanent(string? routeName, object? routeV
         /// </summary>
         /// <param name="routeName">The name of the route.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoutePermanent(string? routeName, string? fragment)
             => RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment);
 
@@ -1161,7 +1223,7 @@ public static IResult RedirectToRoutePermanent(string? routeName, string? fragme
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The parameters for a route.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult RedirectToRoutePermanent(
             string? routeName,
             object? routeValues,
@@ -1178,7 +1240,7 @@ public static IResult RedirectToRoutePermanent(
         /// <param name="routeName">The name of the route.</param>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
         /// <param name="fragment">The fragment to add to the URL.</param>
-        /// <returns>The created <see cref="RedirectToRouteResult"/> for the response.</returns>       
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>       
         public static IResult RedirectToRoutePermanentPreserveMethod(
             string? routeName = null,
             object? routeValues = null,
@@ -1202,101 +1264,101 @@ public static IResult StatusCode(int statusCode)
             => new StatusCodeResult(statusCode);
 
         /// <summary>
-        /// Creates an <see cref="NotFoundResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
+        /// Produces a <see cref="StatusCodes.Status404NotFound"/> response.
         /// </summary>
-        /// <returns>The created <see cref="NotFoundResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult NotFound()
             => new NotFoundResult();
 
         /// <summary>
-        /// Creates an <see cref="NotFoundObjectResult"/> that produces a <see cref="StatusCodes.Status404NotFound"/> response.
+        /// Produces a <see cref="StatusCodes.Status404NotFound"/> response.
         /// </summary>
-        /// <returns>The created <see cref="NotFoundObjectResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult NotFound(object? value)
             => new NotFoundObjectResult(value);
 
         /// <summary>
-        /// Creates an <see cref="UnauthorizedResult"/> that produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
+        /// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
         /// </summary>
-        /// <returns>The created <see cref="UnauthorizedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Unauthorized()
             => new UnauthorizedResult();
 
         /// <summary>
-        /// Creates an <see cref="BadRequestResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
+        /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response.
         /// </summary>
-        /// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult BadRequest()
             => new BadRequestResult();
 
         /// <summary>
-        /// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
+        /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response.
         /// </summary>
-        /// <param name="error">An error object to be returned to the client.</param>
-        /// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
+        /// <param name="error">An error object to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult BadRequest(object? error)
             => new BadRequestObjectResult(error);
 
         /// <summary>
-        /// Creates an <see cref="ConflictResult"/> that produces a <see cref="StatusCodes.Status409Conflict"/> response.
+        /// Produces a <see cref="StatusCodes.Status409Conflict"/> response.
         /// </summary>
-        /// <returns>The created <see cref="ConflictResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Conflict()
             => new ConflictResult();
 
         /// <summary>
-        /// Creates an <see cref="ConflictObjectResult"/> that produces a <see cref="StatusCodes.Status409Conflict"/> response.
+        /// Produces a <see cref="StatusCodes.Status409Conflict"/> response.
         /// </summary>
-        /// <param name="error">Contains errors to be returned to the client.</param>
-        /// <returns>The created <see cref="ConflictObjectResult"/> for the response.</returns>
+        /// <param name="error">An error object to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Conflict(object? error)
             => new ConflictObjectResult(error);
 
         /// <summary>
-        /// Creates a <see cref="NoContentResult"/> object that produces an empty
-        /// <see cref="StatusCodes.Status204NoContent"/> response.
+        /// Produces a <see cref="StatusCodes.Status204NoContent"/> response.
         /// </summary>
-        /// <returns>The created <see cref="NoContentResult"/> object for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult NoContent()
             => new NoContentResult();
 
         /// <summary>
-        /// Creates a <see cref="OkResult"/> object that produces a empty <see cref="StatusCodes.Status200OK"/> response.
+        /// Produces a <see cref="StatusCodes.Status200OK"/> response.
         /// </summary>
-        /// <returns>The created <see cref="OkResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Ok()
             => new OkResult();
 
         /// <summary>
-        /// Creates an <see cref="OkObjectResult"/> object that produces a <see cref="StatusCodes.Status200OK"/> response.
+        /// Produces a <see cref="StatusCodes.Status200OK"/> response.
         /// </summary>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="OkObjectResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Ok(object? value)
             => new OkObjectResult(value);
 
         /// <summary>
-        /// Creates an <see cref="UnprocessableEntityResult"/> that produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
+        /// Produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
         /// </summary>
-        /// <returns>The created <see cref="UnprocessableEntityResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult UnprocessableEntity()
             => new UnprocessableEntityResult();
 
         /// <summary>
-        /// Creates an <see cref="UnprocessableEntityObjectResult"/> that produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
+        /// Produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
         /// </summary>
-        /// <param name="error">An error object to be returned to the client.</param>
-        /// <returns>The created <see cref="UnprocessableEntityObjectResult"/> for the response.</returns>
+        /// <param name="error">An error object to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult UnprocessableEntity(object? error)
             => new UnprocessableEntityObjectResult(error);
 
         #region CreatedResult
         /// <summary>
-        /// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// Produces a <see cref="StatusCodes.Status201Created"/> response.
         /// </summary>
         /// <param name="uri">The URI at which the content has been created.</param>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Created(string uri, object? value)
         {
             if (uri == null)
@@ -1308,11 +1370,11 @@ public static IResult Created(string uri, object? value)
         }
 
         /// <summary>
-        /// Creates a <see cref="CreatedResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// Produces a <see cref="StatusCodes.Status201Created"/> response.
         /// </summary>
         /// <param name="uri">The URI at which the content has been created.</param>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Created(Uri uri, object? value)
         {
             if (uri == null)
@@ -1324,30 +1386,30 @@ public static IResult Created(Uri uri, object? value)
         }
 
         /// <summary>
-        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// Produces a <see cref="StatusCodes.Status201Created"/> response.
         /// </summary>
         /// <param name="routeName">The name of the route to use for generating the URL.</param>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult CreatedAtRoute(string? routeName, object? value)
             => CreatedAtRoute(routeName, routeValues: null, value: value);
 
         /// <summary>
-        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// Produces a <see cref="StatusCodes.Status201Created"/> response.
         /// </summary>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult CreatedAtRoute(object? routeValues, object? value)
             => CreatedAtRoute(routeName: null, routeValues: routeValues, value: value);
 
         /// <summary>
-        /// Creates a <see cref="CreatedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status201Created"/> response.
+        /// Produces a <see cref="StatusCodes.Status201Created"/> response.
         /// </summary>
         /// <param name="routeName">The name of the route to use for generating the URL.</param>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
-        /// <param name="value">The content value to format in the response body.</param>
-        /// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
+        /// <param name="value">The value to be included in the HTTP response body.</param>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult CreatedAtRoute(string? routeName, object? routeValues, object? value)
             => new CreatedAtRouteResult(routeName, routeValues, value);
 
@@ -1355,25 +1417,25 @@ public static IResult CreatedAtRoute(string? routeName, object? routeValues, obj
 
         #region AcceptedResult
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted()
             => new AcceptedResult();
 
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="value">The optional content value to format in the response body.</param>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted(object? value)
             => new AcceptedResult(location: null, value: value);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.</param>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted(Uri uri)
         {
             if (uri == null)
@@ -1385,28 +1447,28 @@ public static IResult Accepted(Uri uri)
         }
 
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="uri">The optional URI with the location at which the status of requested content can be monitored.</param>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted(string? uri)
             => new AcceptedResult(location: uri, value: null);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
         /// <param name="value">The optional content value to format in the response body.</param>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted(string? uri, object? value)
             => new AcceptedResult(uri, value);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
         /// <param name="value">The optional content value to format in the response body.</param>
-        /// <returns>The created <see cref="AcceptedResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult Accepted(Uri uri, object? value)
         {
             if (uri == null)
@@ -1418,46 +1480,46 @@ public static IResult Accepted(Uri uri, object? value)
         }
 
         /// <summary>
-        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
-        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult AcceptedAtRoute(object? routeValues)
             => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="routeName">The name of the route to use for generating the URL.</param>
-        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult AcceptedAtRoute(string? routeName)
             => AcceptedAtRoute(routeName, routeValues: null, value: null);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="routeName">The name of the route to use for generating the URL.</param>
         ///<param name="routeValues">The route data to use for generating the URL.</param>
-        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult AcceptedAtRoute(string? routeName, object? routeValues)
             => AcceptedAtRoute(routeName, routeValues, value: null);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
         /// <param name="value">The optional content value to format in the response body.</param>
-        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult AcceptedAtRoute(object? routeValues, object? value)
             => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value);
 
         /// <summary>
-        /// Creates a <see cref="AcceptedAtRouteResult"/> object that produces a <see cref="StatusCodes.Status202Accepted"/> response.
+        /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
         /// </summary>
         /// <param name="routeName">The name of the route to use for generating the URL.</param>
         /// <param name="routeValues">The route data to use for generating the URL.</param>
         /// <param name="value">The optional content value to format in the response body.</param>
-        /// <returns>The created <see cref="AcceptedAtRouteResult"/> for the response.</returns>
+        /// <returns>The created <see cref="IResult"/> for the response.</returns>
         public static IResult AcceptedAtRoute(string? routeName, object? routeValues, object? value)
             => new AcceptedAtRouteResult(routeName, routeValues, value);
         #endregion